| 1 | /** |
| 2 | * Copyright (C) 2006 NetMind Consulting Bt. |
| 3 | * |
| 4 | * This library is free software; you can redistribute it and/or |
| 5 | * modify it under the terms of the GNU Lesser General Public |
| 6 | * License as published by the Free Software Foundation; either |
| 7 | * version 3 of the License, or (at your option) any later version. |
| 8 | * |
| 9 | * This library is distributed in the hope that it will be useful, |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 12 | * Lesser General Public License for more details. |
| 13 | * |
| 14 | * You should have received a copy of the GNU Lesser General Public |
| 15 | * License along with this library; if not, write to the Free Software |
| 16 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 17 | */ |
| 18 | |
| 19 | package hu.netmind.beankeeper; |
| 20 | |
| 21 | import hu.netmind.beankeeper.service.impl.StoreContextImpl; |
| 22 | import hu.netmind.beankeeper.service.StoreContext; |
| 23 | import java.util.*; |
| 24 | import org.apache.log4j.Logger; |
| 25 | import javax.sql.DataSource; |
| 26 | import hu.netmind.beankeeper.transaction.TransactionTracker; |
| 27 | import hu.netmind.beankeeper.lock.LockTracker; |
| 28 | import hu.netmind.beankeeper.config.ConfigurationTracker; |
| 29 | import hu.netmind.beankeeper.event.EventDispatcher; |
| 30 | import hu.netmind.beankeeper.object.PersistenceMetaData; |
| 31 | import hu.netmind.beankeeper.object.ObjectTracker; |
| 32 | import hu.netmind.beankeeper.store.StoreService; |
| 33 | import hu.netmind.beankeeper.query.QueryService; |
| 34 | import hu.netmind.beankeeper.type.TypeHandlerTracker; |
| 35 | |
| 36 | /** |
| 37 | * This store class is the entry point to the persistence library. To |
| 38 | * store, remove or select given objects, just use the appropriate |
| 39 | * methods. |
| 40 | * @author Brautigam Robert |
| 41 | * @version Revision: $Revision$ |
| 42 | */ |
| 43 | public class Store |
| 44 | { |
| 45 | private static Logger logger = Logger.getLogger(Store.class); |
| 46 | |
| 47 | private StoreContext context = null; |
| 48 | private StoreService store = null; |
| 49 | private LockTracker lockTracker = null; |
| 50 | private ConfigurationTracker config = null; |
| 51 | private EventDispatcher eventDispatcher = null; |
| 52 | private TransactionTracker transactionTracker = null; |
| 53 | private ObjectTracker objectTracker = null; |
| 54 | private QueryService queryService = null; |
| 55 | private TypeHandlerTracker typeHandlerTracker = null; |
| 56 | |
| 57 | /** |
| 58 | * Instantiate a store. Note that you should only make one |
| 59 | * store instance for each datasource you might want to use, all |
| 60 | * methods are thread-safe, so you can use the single instance |
| 61 | * in more threads. |
| 62 | * @param driverClass The driver class to register. |
| 63 | * @param url The driver's jdbc url (including username and password). |
| 64 | */ |
| 65 | public Store(String driverClass, String url) |
| 66 | { |
| 67 | Map parameters = new HashMap(); |
| 68 | parameters.put(StoreContext.PARAM_DRIVERCLASS,driverClass); |
| 69 | parameters.put(StoreContext.PARAM_DRIVERURL,url); |
| 70 | init(parameters); |
| 71 | } |
| 72 | |
| 73 | /** |
| 74 | * Instantiate a store. Note that you should only make one |
| 75 | * store instance for each datasource you might want to use, all |
| 76 | * methods are thread-safe, so you can use the single instance |
| 77 | * in more threads. |
| 78 | */ |
| 79 | public Store(DataSource dataSource) |
| 80 | { |
| 81 | Map parameters = new HashMap(); |
| 82 | parameters.put(StoreContext.PARAM_DATASOURCE,dataSource); |
| 83 | init(parameters); |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * Construct this store with the given parameters. |
| 88 | */ |
| 89 | private void init(Map parameters) |
| 90 | { |
| 91 | // Create context |
| 92 | context = new StoreContextImpl(); |
| 93 | context.init(parameters); |
| 94 | // Get services |
| 95 | context.injectServices(this); |
| 96 | } |
| 97 | |
| 98 | /** |
| 99 | * Close the store, and release all resources. This method is automatically |
| 100 | * invoked as a JVM shutdown hook, so it does not have to be called at all. |
| 101 | */ |
| 102 | public void close() |
| 103 | { |
| 104 | context.release(); |
| 105 | } |
| 106 | |
| 107 | /** |
| 108 | * Get the transaction tracker associated with this store. The transaction |
| 109 | * tracker can be used to create transactions, and listen for transactional |
| 110 | * events. |
| 111 | * @return The transaction tracker. |
| 112 | */ |
| 113 | public TransactionTracker getTransactionTracker() |
| 114 | { |
| 115 | return transactionTracker; |
| 116 | } |
| 117 | |
| 118 | /** |
| 119 | * Get the configuration tracker for this Store. |
| 120 | */ |
| 121 | public ConfigurationTracker getConfigurationTracker() |
| 122 | { |
| 123 | return config; |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * Get the lock tracker. This tracker can be used to lock and unlock |
| 128 | * objects for exclusive operations. |
| 129 | */ |
| 130 | public LockTracker getLockTracker() |
| 131 | { |
| 132 | return lockTracker; |
| 133 | } |
| 134 | |
| 135 | /** |
| 136 | * Get the type handler which is responisble for saving fields |
| 137 | * other than primitive types. |
| 138 | */ |
| 139 | public TypeHandlerTracker getTypeHandlerTracker() |
| 140 | { |
| 141 | return typeHandlerTracker; |
| 142 | } |
| 143 | |
| 144 | /** |
| 145 | * Get the event dispatcher in which the caller may register |
| 146 | * listeners. |
| 147 | */ |
| 148 | public EventDispatcher getEventDispatcher() |
| 149 | { |
| 150 | return eventDispatcher; |
| 151 | } |
| 152 | |
| 153 | /** |
| 154 | * Get the persistence id for an object. This method always returns |
| 155 | * a valid id, even if the object is not saved, or otherwise has |
| 156 | * no persistence id. If the object is later saved, this persistence id |
| 157 | * is always preserved.<br> |
| 158 | * Same as: <code>getPersistenceMetaData(obj).getPersistenceId()</code>. |
| 159 | * @return A persistence id, and never null. |
| 160 | */ |
| 161 | public Long getPersistenceId(Object obj) |
| 162 | { |
| 163 | return getPersistenceMetaData(obj).getPersistenceId(); |
| 164 | } |
| 165 | |
| 166 | /** |
| 167 | * Get the persistence meta-data object for a given object. Metadata |
| 168 | * is always available, even for non-saved objects. |
| 169 | */ |
| 170 | public PersistenceMetaData getPersistenceMetaData(Object obj) |
| 171 | { |
| 172 | return objectTracker.getMetaData(obj); |
| 173 | } |
| 174 | |
| 175 | |
| 176 | /** |
| 177 | * Save the given object to the store. The given object's all private |
| 178 | * non-transient fields will be saved. If the object was not selected |
| 179 | * from the store, and not yet saved, it will be created in the store, |
| 180 | * and a unique id will be assigned, so all subsequent calls to save |
| 181 | * the given object will only modify the already existing instance in |
| 182 | * store. A few tips:<br> |
| 183 | * <ul> |
| 184 | * <li>Use simple beans. Although this library does not scan |
| 185 | * methods to determine the attributes to save, it is a good |
| 186 | * idea to simplify work with them.</li> |
| 187 | * <li>If you do not use simple beans, watch out that your |
| 188 | * object does not reference unnecessary objects, because |
| 189 | * if it does, all will be saved/inserted and tracked.</li> |
| 190 | * <li>You CAN use objects which reference other beans though. |
| 191 | * But beware, that all objects which are directly referenced |
| 192 | * will be loaded when the parent object loads.</li> |
| 193 | * <li>You CAN use Map, and List types in your |
| 194 | * beans. Check the documentation.</li> |
| 195 | * </ul> |
| 196 | * <i>Note:</i>A save operation is not recursive. It does not traverse |
| 197 | * the object hierarchy, only saves the object given, and does not save |
| 198 | * objects referenced, except when referenced object does not exist |
| 199 | * yet. If a referenced object does not yet exist, it will be inserted |
| 200 | * into database (and recursively all referenced objects of that object). |
| 201 | * <i>Implementation note:</i>This class is the intelligent part of |
| 202 | * the framework, an intentionally so. This is the class that coordinates |
| 203 | * all other classes, trackers and functions together. |
| 204 | * @param obj The object to save. |
| 205 | * @throws StoreException If save is not successfull. |
| 206 | */ |
| 207 | public void save(Object obj) |
| 208 | { |
| 209 | store.save(obj); |
| 210 | } |
| 211 | |
| 212 | /** |
| 213 | * Remove the object given. If the object is not stored yet, no |
| 214 | * operation will take place. |
| 215 | * @param obj The object to remove. |
| 216 | * @throws StoreException If remove is not successfull. |
| 217 | */ |
| 218 | public void remove(Object obj) |
| 219 | { |
| 220 | store.remove(obj); |
| 221 | } |
| 222 | |
| 223 | /** |
| 224 | * Query an object from the datastore. The List returned is |
| 225 | * a lazy list, the implementation tries to limit the communication |
| 226 | * with the database layer, as much as possible and pratical. Only |
| 227 | * parts of the list will be loaded when an item is referenced, not |
| 228 | * the whole list. Some features of the query language:<br> |
| 229 | * <pre>find book where book.name='Snow Crash'</pre> |
| 230 | * The statement always starts with the keyword 'find', and all |
| 231 | * keywords and parts of the statement not between apostrophs are |
| 232 | * case in-sensitive.<br> |
| 233 | * The second word of the statement determines |
| 234 | * the class you are trying to find. You can abbreviate the classname |
| 235 | * (strip the package) if it is unique, but you can use the full |
| 236 | * name (com.acme.book) if you wish, but then you MUST provide an |
| 237 | * alias name (see below)<br> |
| 238 | * The following parts are all optional. First, there can be |
| 239 | * a select statement, to specify which objects to select which are |
| 240 | * instances of given class. If you want to have a where part, the |
| 241 | * third word should be 'where'. After it there should be an |
| 242 | * expression almost as in SQL. Parts of the expression can be:<br> |
| 243 | * <ul> |
| 244 | * <li><strong>Member attributes of classes.</strong> The class given previously |
| 245 | * (the target of statement) is always available as declared. If |
| 246 | * you wish to use other classes, they can be referenced by |
| 247 | * class (abbreviated or fully declared). For example |
| 248 | * <pre>find book where book.author=author and author.birthdate=1959</pre> |
| 249 | * Of course Book, and Author classes must exits in the store with |
| 250 | * given attributes. |
| 251 | * Note also, that the above statement can be simply written: |
| 252 | * <pre>find book where book.author.birthdate=1959</pre> |
| 253 | * You can name the classes you are referencing for later |
| 254 | * use (handy if you must "join" the class with itself): |
| 255 | * <pre>find book where book.author=otherbook(book).author and otherbook.name='Snow Crash'</pre> |
| 256 | * Here, the "otherbook" does not refer to a class, it is only another |
| 257 | * name for the class book, and means, that is should be not the same |
| 258 | * instance as the one referenced with "book". |
| 259 | * </li> |
| 260 | * <li><strong>"Now" constant</strong>: For easy use the current date/time |
| 261 | * is represented with the special word 'now'. So you can write: |
| 262 | * <pre>find movie where movie.startdate > now</pre> |
| 263 | * This also means, that attributes named 'now' must be escaped (prefixed |
| 264 | * with it's table name). |
| 265 | * <li><strong>Constants</strong>: Numbers and strings, see examples above. |
| 266 | * Dates and objects can be given by using the question mark (?), and adding the object |
| 267 | * as parameters (same as in jdbc).</li> |
| 268 | * <li><strong>The operators</strong>: <, >, =, !=, like, is null, not null, is not null. |
| 269 | * Note however, that not all database backends are required to support |
| 270 | * all of these. If they are not supported, an exception will be thrown.</li> |
| 271 | * <li><strong>Logical operators</strong>: or, and, not. See examples above.</li> |
| 272 | * <li><strong>Grouping with parenthesis.</strong> As usual in expressions, |
| 273 | * you can use grouping: |
| 274 | * <pre>find book where ((book.author.firstname='Neal') and (book.author.lastname='Stephenson'))</pre> |
| 275 | * </li> |
| 276 | * <li><strong>Special container operator</strong>: contains. |
| 277 | * These are used in conjunction with container types such as Map and List: |
| 278 | * <pre>find book where book.genres contains genre and genre.name='postcyberpunk'</pre> |
| 279 | * <pre>find author where author.books contains book and book.name='Snow Crash'</pre> |
| 280 | * A container operator can not be negated, an exception will be thrown, |
| 281 | * if the expression would try to do that. |
| 282 | * </li> |
| 283 | * <li><strong>Special map operator</strong>: [, ]. These are used when |
| 284 | * referencing a Map. Note also, that [] can only contain strings. |
| 285 | * <pre>find book where book.metadata['author']=author and author.name='Neal Stephenson</pre> |
| 286 | * However, if after a map operator an attribute is referenced, |
| 287 | * there is a mandatory class specifier: |
| 288 | * <pre>find book where book.metadata['author'](author).name='Neal Stephenson</pre> |
| 289 | * </li> |
| 290 | * </ul><br> |
| 291 | * You can also sort the result list with the 'order by' command: |
| 292 | * <pre>find book order by book.name asc</pre> |
| 293 | * The order by command takes attributes as aguments. |
| 294 | * You can give more than one attribute separated by commas. |
| 295 | * Also you can append 'asc' (ascending) or 'desc' (descending) to |
| 296 | * mark the direction of sort. |
| 297 | * <pre>find book order by book.author.name asc, book.name desc</pre> |
| 298 | * Note, that method will silently return an empty list, if the |
| 299 | * specified table or one specified in where clause does not exist.<br> |
| 300 | * For more detailed information, check the documentation. |
| 301 | * @param statement The query statement to select. |
| 302 | */ |
| 303 | public List find(String statement) |
| 304 | { |
| 305 | return queryService.find(statement,null); |
| 306 | } |
| 307 | |
| 308 | /** |
| 309 | * Same as <code>find(statement)</code>. When a statement contains |
| 310 | * the question mark (?), the object which should be in the place of |
| 311 | * the mark should be given as parameters (parameters are usually of |
| 312 | * Date, or custom classes). |
| 313 | * @param statement The query statement to execute. |
| 314 | * @param parameters The parameters. |
| 315 | */ |
| 316 | public List find(String statement, Object[] parameters) |
| 317 | { |
| 318 | return queryService.find(statement,parameters); |
| 319 | } |
| 320 | |
| 321 | /** |
| 322 | * Same as <code>find(statement,parameters)</code>, but the result should be |
| 323 | * a single object. |
| 324 | * @param statement The query statement to execute. |
| 325 | * @param parameters The parameters to the statement. |
| 326 | * @return The object selected, or null if no such object exists. If |
| 327 | * the result contains more objects, an arbitrary one is selected. |
| 328 | */ |
| 329 | public Object findSingle(String statement, Object[] parameters) |
| 330 | { |
| 331 | return queryService.findSingle(statement,parameters); |
| 332 | } |
| 333 | |
| 334 | /** |
| 335 | * Same as <code>find(statement)</code>, but the result should be |
| 336 | * a single object. |
| 337 | * @param statement The query statement to execute. |
| 338 | * @return The object selected, or null if no such object exists. If |
| 339 | * the result contains more objects, an arbitrary one is selected. |
| 340 | */ |
| 341 | public Object findSingle(String statement) |
| 342 | { |
| 343 | return queryService.findSingle(statement,null); |
| 344 | } |
| 345 | |
| 346 | } |
| 347 | |
| 348 | |
| 349 | |