001: /*
002: * Copyright (c) 2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: *
016: */
017:
018: package org.jpublish.module.cayenne;
019:
020: import org.apache.cayenne.*;
021: import org.apache.cayenne.access.DataContext;
022: import org.apache.cayenne.exp.Expression;
023: import org.apache.cayenne.exp.ExpressionFactory;
024: import org.apache.cayenne.map.DbAttribute;
025: import org.apache.cayenne.map.DbEntity;
026: import org.apache.cayenne.map.ObjEntity;
027: import org.apache.cayenne.query.ObjectIdQuery;
028: import org.apache.cayenne.query.Query;
029: import org.apache.cayenne.query.SelectQuery;
030: import org.apache.commons.lang.Validate;
031:
032: import java.util.Collections;
033: import java.util.List;
034: import java.util.Map;
035:
036: /**
037: * Provides base Cayenne data access object or service class to extend, following
038: * the Spring DAO template pattern. This class uses thread bound
039: * <tt>DataContext</tt> for all data access operations.
040: * <p/>
041: * This class is designed to be extended by custom DAO or Service subclasses
042: * which provide their own public interface. All methods on CayenneTemplate have
043: * protected visibility so they are not publicly visible on the custom subclasses.
044: * <p/>
045: * CayenneTemplate provides many convience DataContext methods using the
046: * DataContext object bound to the current thread.
047: *
048: * @author Malcolm Edgar
049: * @author Andrei Adamchik
050: * @author <a href="mailto:florin.patrascu@gmail.com">Florin T.PATRASCU</a>
051: * @since $Revision$ (created: Sep 22, 2007 11:45:03 PM)
052: */
053: public class CayenneTemplate {
054:
055: // ------------------------------------------------------ Protected Methods
056:
057: /**
058: * Instantiates new object and registers it with itself. Object class must
059: * have a default constructor.
060: *
061: * @param dataObjectClass the data object class to create and register
062: * @return the new registered data object
063: */
064: protected Persistent createAndRegisterNewObject(
065: Class dataObjectClass) {
066: return getDataContext().createAndRegisterNewObject(
067: dataObjectClass);
068: }
069:
070: /**
071: * Commit any changes in the thread local DataContext.
072: */
073: protected void commitChanges() {
074: getDataContext().commitChanges();
075: }
076:
077: /**
078: * Schedules an object for deletion on the next commit of this DataContext. Object's
079: * persistence state is changed to PersistenceState.DELETED; objects related to this
080: * object are processed according to delete rules, i.e. relationships can be unset
081: * ("nullify" rule), deletion operation is cascaded (cascade rule).
082: *
083: * @param dataObject a persistent data object that we want to delete
084: * @throws org.apache.cayenne.DeleteDenyException
085: * if a DENY delete rule
086: * is applicable for object deletion
087: */
088: protected void deleteObject(DataObject dataObject)
089: throws DeleteDenyException {
090: Validate.notNull(dataObject, "Null dataObject parameter");
091:
092: getDataContext().deleteObject(dataObject);
093: }
094:
095: /**
096: * Find the data object for the specified class, property name and property
097: * value, or null if no data object was found.
098: *
099: * @param dataObjectClass the data object class to find
100: * @param property the name of the property
101: * @param value the value of the property
102: * @return the data object for the specified class, property name and property value
103: * @throws RuntimeException if more than one data object was identified for the
104: * given property name and value
105: */
106: protected DataObject findObject(Class dataObjectClass,
107: String property, Object value) {
108:
109: List list = performQuery(dataObjectClass, property, value);
110:
111: if (list.size() == 1) {
112: return (DataObject) list.get(0);
113:
114: } else if (list.size() > 1) {
115: String msg = "SelectQuery for " + dataObjectClass.getName()
116: + " where " + property + " equals " + value
117: + " returned " + list.size() + " rows";
118: throw new RuntimeException(msg);
119:
120: } else {
121: return null;
122: }
123: }
124:
125: /**
126: * Return the thread local DataContext.
127: *
128: * @return the thread local DataContext
129: * @throws IllegalStateException if there is no DataContext bound to the current thread
130: */
131: protected DataContext getDataContext() {
132: return DataContext.getThreadDataContext();
133: }
134:
135: /**
136: * Perform a database query returning the data object specified by the
137: * class and the primary key. This method will perform a database query
138: * and refresh the object cache.
139: *
140: * @param doClass the data object class to retrieve
141: * @param id the data object primary key
142: * @return the data object for the given class and id
143: */
144: protected DataObject getObjectForPK(Class doClass, Object id) {
145: return getObjectForPK(doClass, id, true);
146: }
147:
148: /**
149: * Perform a query returning the data object specified by the
150: * class and the primary key value. If the refresh parameter is true a
151: * database query will be performed, otherwise the a query against the
152: * object cache will be performed first.
153: *
154: * @param dataObjectClass the data object class to retrieve
155: * @param id the data object primary key
156: * @param refresh the refresh the object cache mode
157: * @return the data object for the given class and id
158: */
159: protected DataObject getObjectForPK(Class dataObjectClass,
160: Object id, boolean refresh) {
161: Validate.notNull(dataObjectClass,
162: "Null dataObjectClass parameter.");
163:
164: ObjEntity objEntity = getDataContext().getEntityResolver()
165: .lookupObjEntity(dataObjectClass);
166:
167: if (objEntity == null) {
168: throw new CayenneRuntimeException(
169: "Unmapped DataObject Class: "
170: + dataObjectClass.getName());
171: }
172:
173: String pkName = getPkName(dataObjectClass);
174:
175: ObjectId objectId = new ObjectId(objEntity.getName(), pkName,
176: id);
177:
178: int refreshMode = (refresh) ? ObjectIdQuery.CACHE_REFRESH
179: : ObjectIdQuery.CACHE;
180:
181: ObjectIdQuery objectIdQuery = new ObjectIdQuery(objectId,
182: false, refreshMode);
183:
184: return (DataObject) DataObjectUtils.objectForQuery(
185: getDataContext(), objectIdQuery);
186: }
187:
188: /**
189: * Return the database primary key column name for the given data object.
190: *
191: * @param dataObjectClass the class of the data object
192: * @return the primary key column name
193: */
194: protected String getPkName(Class dataObjectClass) {
195: Validate.notNull(dataObjectClass,
196: "Null dataObjectClass parameter.");
197:
198: ObjEntity objEntity = getDataContext().getEntityResolver()
199: .lookupObjEntity(dataObjectClass);
200:
201: if (objEntity == null) {
202: throw new CayenneRuntimeException(
203: "Unmapped DataObject Class: "
204: + dataObjectClass.getName());
205: }
206:
207: DbEntity dbEntity = objEntity.getDbEntity();
208: if (dbEntity == null) {
209: throw new CayenneRuntimeException(
210: "No DbEntity for ObjEntity: " + objEntity.getName());
211: }
212:
213: List pkAttributes = dbEntity.getPrimaryKey();
214: if (pkAttributes.size() != 1) {
215: throw new CayenneRuntimeException("PK contains "
216: + pkAttributes.size() + " columns, expected 1.");
217: }
218:
219: DbAttribute attr = (DbAttribute) pkAttributes.get(0);
220:
221: return attr.getName();
222: }
223:
224: /**
225: * Performs a single selecting query. Various query setting control the behavior of
226: * this method and the results returned:
227: * <ul>
228: * <li>Query caching policy defines whether the results are retrieved from cache or
229: * fetched from the database. Note that queries that use caching must have a name that
230: * is used as a caching key.
231: * </li>
232: * <li>Query refreshing policy controls whether to refresh existing data objects and
233: * ignore any cached values.
234: * </li>
235: * <li>Query data rows policy defines whether the result should be returned as
236: * DataObjects or DataRows.
237: * </li>
238: * </ul>
239: *
240: * @param query the query to perform
241: * @return a list of DataObjects or a DataRows for the query
242: */
243: protected List performQuery(Query query) {
244: return getDataContext().performQuery(query);
245: }
246:
247: /**
248: * Returns a list of objects or DataRows for a named query stored in one of the
249: * DataMaps. Internally Cayenne uses a caching policy defined in the named query. If
250: * refresh flag is true, a refresh is forced no matter what the caching policy is.
251: *
252: * @param queryName a name of a GenericSelectQuery defined in one of the DataMaps. If
253: * no such query is defined, this method will throw a CayenneRuntimeException
254: * @param refresh A flag that determines whether refresh of <b>cached lists</b>
255: * is required in case a query uses caching.
256: * @return the list of data object or DataRows for the named query
257: */
258: protected List performQuery(String queryName, boolean refresh) {
259: return getDataContext().performQuery(queryName, refresh);
260: }
261:
262: /**
263: * Returns a list of objects or DataRows for a named query stored in one of the
264: * DataMaps. Internally Cayenne uses a caching policy defined in the named query. If
265: * refresh flag is true, a refresh is forced no matter what the caching policy is.
266: *
267: * @param queryName a name of a GenericSelectQuery defined in one of the DataMaps. If
268: * no such query is defined, this method will throw a CayenneRuntimeException
269: * @param parameters A map of parameters to use with stored query
270: * @param refresh A flag that determines whether refresh of <b>cached lists</b>
271: * is required in case a query uses caching.
272: * @return the list of data object or DataRows for the named query
273: */
274: protected List performQuery(String queryName, Map parameters,
275: boolean refresh) {
276:
277: return getDataContext().performQuery(queryName, parameters,
278: refresh);
279: }
280:
281: /**
282: * Return a list of data object of the specified class for the given property
283: * and value.
284: *
285: * @param dataObjectClass the data object class to return
286: * @param property the name of the property to select
287: * @param value the property value to select
288: * @return a list of data objects for the given class and property name and value
289: */
290: protected List performQuery(Class dataObjectClass, String property,
291: Object value) {
292:
293: Validate.notNull(dataObjectClass,
294: "Null dataObjectClass parameter");
295: Validate.notNull(property, "Null property parameter");
296: Validate.notNull(value, "Null value parameter");
297:
298: Expression qual = ExpressionFactory.matchExp(property, value);
299: return performQuery(new SelectQuery(dataObjectClass, qual));
300: }
301:
302: /**
303: * Performs a single database query that does not select rows. Returns an
304: * array of update counts.
305: *
306: * @param query the query to perform
307: * @return the array of update counts
308: */
309: protected int[] performNonSelectingQuery(Query query) {
310: return getDataContext().performNonSelectingQuery(query);
311: }
312:
313: /**
314: * Performs a named mapped query that does not select rows. Returns an array
315: * of update counts.
316: *
317: * @param queryName the name of the query to perform
318: * @return the array of update counts
319: */
320: protected int[] performNonSelectingQuery(String queryName) {
321: return getDataContext().performNonSelectingQuery(queryName);
322: }
323:
324: /**
325: * Performs a named mapped non-selecting query using a map of parameters.
326: * Returns an array of update counts.
327: *
328: * @param queryName the name of the query to perform
329: * @param parameters the Map of query paramater names and values
330: * @return the array of update counts
331: */
332: protected int[] performNonSelectingQuery(String queryName,
333: Map parameters) {
334: return getDataContext().performNonSelectingQuery(queryName,
335: parameters);
336: }
337:
338: /**
339: * Registers a transient object with the context, recursively registering all
340: * transient DataObjects attached to this object via relationships.
341: *
342: * @param dataObject new object that needs to be made persistent
343: */
344: protected void registerNewObject(DataObject dataObject) {
345: getDataContext().registerNewObject(dataObject);
346: }
347:
348: /**
349: * Reverts any changes that have occurred to objects registered in the
350: * thread local DataContext.
351: */
352: protected void rollbackChanges() {
353: getDataContext().rollbackChanges();
354: }
355:
356: /**
357: * Return a Map containing the given key name and value.
358: *
359: * @param key the map key name
360: * @param value the map key value
361: * @return a Map containing the given key name and value
362: */
363: protected Map toMap(String key, Object value) {
364: return Collections.singletonMap(key, value);
365: }
366:
367: }
|