0001: /*
0002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
0003: * Distributed under the terms of either:
0004: * - the common development and distribution license (CDDL), v1.0; or
0005: * - the GNU Lesser General Public License, v2.1 or later
0006: * $Id: ContentQueryManager.java 3848 2007-07-12 08:55:48Z gbevin $
0007: */
0008: package com.uwyn.rife.cmf.dam;
0009:
0010: import com.uwyn.rife.cmf.dam.exceptions.*;
0011:
0012: import com.uwyn.rife.cmf.Content;
0013: import com.uwyn.rife.cmf.dam.contentmanagers.DatabaseContentFactory;
0014: import com.uwyn.rife.database.Datasource;
0015: import com.uwyn.rife.database.DbQueryManager;
0016: import com.uwyn.rife.database.DbTransactionUser;
0017: import com.uwyn.rife.database.exceptions.DatabaseException;
0018: import com.uwyn.rife.database.querymanagers.generic.GenericQueryManager;
0019: import com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerDelegate;
0020: import com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerListener;
0021: import com.uwyn.rife.database.querymanagers.generic.RestoreQuery;
0022: import com.uwyn.rife.engine.Element;
0023: import com.uwyn.rife.site.Constrained;
0024: import com.uwyn.rife.site.ConstrainedProperty;
0025: import com.uwyn.rife.site.ConstrainedUtils;
0026: import com.uwyn.rife.tools.BeanUtils;
0027: import com.uwyn.rife.tools.ClassUtils;
0028: import com.uwyn.rife.tools.ExceptionUtils;
0029: import com.uwyn.rife.tools.InnerClassException;
0030: import com.uwyn.rife.tools.StringUtils;
0031: import com.uwyn.rife.tools.exceptions.BeanUtilsException;
0032: import java.util.Collection;
0033: import java.util.List;
0034: import java.util.logging.Logger;
0035:
0036: /**
0037: * The <code>ContentQueryManager</code> simplifies working with content a lot.
0038: * It extends {@link
0039: * com.uwyn.rife.database.querymanagers.generic.GenericQueryManager
0040: * GenericQueryManager} and is a drop-in replacement that can be used instead.
0041: * The <code>ContentQueryManager</code> class works hand-in-hand with
0042: * CMF-related constraints that are provided via the classes {@link
0043: * com.uwyn.rife.site.Validation Validation} and {@link
0044: * com.uwyn.rife.site.ConstrainedProperty ConstrainedProperty}. The additional constraints
0045: * allow you to provide CMF-related metadata for bean properties while still
0046: * having access to all regular constraints.
0047: * <p>The most important additional constraint is '{@link
0048: * com.uwyn.rife.site.ConstrainedProperty#mimeType(com.uwyn.rife.cmf.MimeType) mimeType}'. Setting this
0049: * constraint directs RIFE to delegate the handling of that property's data to
0050: * the CMF instead of storing it as a regular column in a database table. The
0051: * property content location (i.e. its full path) is generated automatically
0052: * based on the bean class name, the instance's identifier value (i.e. the
0053: * primary key used by <code>GenericQueryManager</code>), and the property
0054: * name. So for example, if you have an instance of the <code>NewsItem</code>
0055: * class whose identifier is <code>23</code>, then the full path that is
0056: * generated for a property named <code>text</code> is '<code>/newsitem/23/text</code>'.
0057: * Note that this always specifies the most recent version of the property,
0058: * but that older versions are also available from the content store.
0059: * <p>Before being able to use the CMF and a <code>ContentQueryManager</code>,
0060: * you must install both of them, as in this example:
0061: * <pre>Datasource ds = Datasources.getRepInstance().getDatasource("datasource");
0062: *DatabaseContentFactory.getInstance(ds).install();
0063: *new ContentQueryManager(ds, NewsItem.class).install();</pre>
0064: * <p>Apart from the handling of content, this query manager also integrates
0065: * the functionalities of the {@link OrdinalManager} class.
0066: * <p>The new '{@link com.uwyn.rife.site.ConstrainedProperty#ordinal(boolean) ordinal}'
0067: * constraint indicates which bean property will be used to order that table
0068: * rows. When saving and deleting beans, the ordinal values will be
0069: * automatically updated in the entire table. The
0070: * <code>ContentQueryManager</code> also provides the {@link
0071: * #move(Constrained, String, OrdinalManager.Direction) move}, {@link
0072: * #up(Constrained, String) up} and {@link #down(Constrained, String) down}
0073: * methods to easily manipulate the order of existing rows.
0074: *
0075: * @author Geert Bevin (gbevin[remove] at uwyn dot com)i
0076: * @version $Revision: 3848 $
0077: * @since 1.0
0078: */
0079: public class ContentQueryManager<T> extends
0080: GenericQueryManagerDelegate<T> implements Cloneable {
0081: private Class mClass = null;
0082: private Class mBackendClass = null;
0083: private DbQueryManager mDbQueryManager = null;
0084: private ContentManager mContentManager = null;
0085: private String mRepository = null;
0086:
0087: private ThreadLocal<T> mDeletedbean = new ThreadLocal<T>();
0088:
0089: /**
0090: * Creates a new <code>ContentQueryManager</code> instance for a specific
0091: * class.
0092: * <p>All content will be stored in a {@link
0093: * com.uwyn.rife.cmf.dam.contentmanagers.DatabaseContent}.
0094: *
0095: * @param datasource the datasource that indicates where the data will be
0096: * stored
0097: * @param klass the class of the bean that will be handled by this
0098: * <code>ContentQueryManager</code>
0099: * @param backendClass the class the will be used by this
0100: * <code>ContentQueryManager</code> to reference data in the backend
0101: * @since 1.0
0102: */
0103: public ContentQueryManager(Datasource datasource, Class<T> klass,
0104: Class backendClass) {
0105: super (datasource, klass, ClassUtils
0106: .shortenClassName(backendClass));
0107:
0108: mClass = klass;
0109: mBackendClass = backendClass;
0110: mDbQueryManager = new DbQueryManager(datasource);
0111: mContentManager = DatabaseContentFactory
0112: .getInstance(datasource);
0113: addListener(new Listener());
0114: }
0115:
0116: /**
0117: * Creates a new <code>ContentQueryManager</code> instance for a specific
0118: * class, but with a different table name for the database storage.
0119: * <p>All content will be stored in a {@link
0120: * com.uwyn.rife.cmf.dam.contentmanagers.DatabaseContent}.
0121: *
0122: * @param datasource the datasource that indicates where the data will be
0123: * stored
0124: * @param klass the class of the bean that will be handled by this
0125: * <code>ContentQueryManager</code>
0126: * @param table the name of the database table in which the non CMF data will
0127: * be stored
0128: * @since 1.6
0129: */
0130: public ContentQueryManager(Datasource datasource, Class<T> klass,
0131: String table) {
0132: super (datasource, klass, table);
0133:
0134: mClass = klass;
0135: mBackendClass = klass;
0136: mDbQueryManager = new DbQueryManager(datasource);
0137: mContentManager = DatabaseContentFactory
0138: .getInstance(datasource);
0139: addListener(new Listener());
0140: }
0141:
0142: /**
0143: * Creates a new <code>ContentQueryManager</code> instance for a specific
0144: * class.
0145: * <p>All content will be stored in a {@link
0146: * com.uwyn.rife.cmf.dam.contentmanagers.DatabaseContent}.
0147: *
0148: * @param datasource the datasource that indicates where the data will be
0149: * stored
0150: * @param klass the class of the bean that will be handled by this
0151: * <code>ContentQueryManager</code>
0152: * @since 1.0
0153: */
0154: public ContentQueryManager(Datasource datasource, Class<T> klass) {
0155: super (datasource, klass);
0156:
0157: mClass = klass;
0158: mBackendClass = klass;
0159: mDbQueryManager = new DbQueryManager(datasource);
0160: mContentManager = DatabaseContentFactory
0161: .getInstance(datasource);
0162: addListener(new Listener());
0163: }
0164:
0165: /**
0166: * Creates a new <code>ContentQueryManager</code> instance for a specific
0167: * class.
0168: * <p>All content will be stored in the provided
0169: * <code>ContentManager</code> instance. This constructor is handy if you
0170: * want to integrate a custom content manager implementation.
0171: *
0172: * @param datasource the datasource that indicates where the data will be
0173: * stored
0174: * @param klass the class of the bean that will be handled by this
0175: * <code>ContentQueryManager</code>
0176: * @param contentManager a <code>ContentManager</code> instance
0177: * @since 1.0
0178: */
0179: public ContentQueryManager(Datasource datasource, Class<T> klass,
0180: ContentManager contentManager) {
0181: super (datasource, klass);
0182:
0183: mClass = klass;
0184: mContentManager = contentManager;
0185: addListener(new Listener());
0186: }
0187:
0188: /**
0189: * Sets the default repository that will be used by this <code>ContentQueryManager</code>.
0190: *
0191: * @return this <code>ContentQueryManager</code>
0192: * @see #getRepository
0193: * @since 1.4
0194: */
0195: public ContentQueryManager<T> repository(String repository) {
0196: mRepository = repository;
0197:
0198: return this ;
0199: }
0200:
0201: /**
0202: * Retrieves the default repository that is used by this <code>ContentQueryManager</code>.
0203: *
0204: * @return this <code>ContentQueryManager</code>'s repository
0205: * @see #repository
0206: * @since 1.4
0207: */
0208: public String getRepository() {
0209: return mRepository;
0210: }
0211:
0212: /**
0213: * Returns the <code>ContentManager</code> that is used to store and
0214: * retrieve the content.
0215: *
0216: * @return the <code>ContentManager</code>
0217: * @since 1.0
0218: */
0219: public ContentManager getContentManager() {
0220: return mContentManager;
0221: }
0222:
0223: /**
0224: * Moves the row that corresponds to the provided bean instance according
0225: * to a property with an ordinal constraint.
0226: *
0227: * @param bean the bean instance that corresponds to the row that has to
0228: * be moved
0229: * @param propertyName the name of the property with an ordinal constraint
0230: * @param direction {@link OrdinalManager#UP} or {@link
0231: * OrdinalManager#DOWN}
0232: * @return <code>true</code> if the row was moved successfully; or
0233: * <p><code>false</code> otherwise
0234: * @since 1.0
0235: */
0236: public boolean move(Constrained bean, String propertyName,
0237: OrdinalManager.Direction direction) {
0238: if (null == bean)
0239: throw new IllegalArgumentException(
0240: "constrained can't be nul");
0241: if (null == propertyName)
0242: throw new IllegalArgumentException(
0243: "propertyName can't be null");
0244: if (0 == propertyName.length())
0245: throw new IllegalArgumentException(
0246: "propertyName can't be empty");
0247:
0248: ConstrainedProperty property = bean
0249: .getConstrainedProperty(propertyName);
0250: if (null == property) {
0251: throw new UnknownConstrainedPropertyException(bean
0252: .getClass(), propertyName);
0253: }
0254:
0255: if (!property.isOrdinal()) {
0256: throw new ExpectedOrdinalConstraintException(bean
0257: .getClass(), propertyName);
0258: }
0259:
0260: // obtain the ordinal value
0261: int ordinal = -1;
0262: try {
0263: Object ordinal_object = BeanUtils.getPropertyValue(bean,
0264: propertyName);
0265: if (!(ordinal_object instanceof Integer)) {
0266: throw new InvalidOrdinalTypeException(bean.getClass(),
0267: propertyName);
0268: }
0269: ordinal = ((Integer) ordinal_object).intValue();
0270: } catch (BeanUtilsException e) {
0271: throw new UnknownOrdinalException(bean.getClass(),
0272: propertyName);
0273: }
0274:
0275: OrdinalManager ordinals = null;
0276:
0277: if (property.hasOrdinalRestriction()) {
0278: String restriction_name = property.getOrdinalRestriction();
0279:
0280: // initialy the ordinal manager, taking the restriction property into account
0281: ordinals = new OrdinalManager(getDatasource(), getTable(),
0282: propertyName, restriction_name);
0283:
0284: // obtain the restriction value
0285: long restriction = -1;
0286: try {
0287: Object restriction_object = BeanUtils.getPropertyValue(
0288: bean, restriction_name);
0289: if (null == restriction_object) {
0290: throw new OrdinalRestrictionCantBeNullException(
0291: bean.getClass(),
0292: property.getPropertyName(),
0293: restriction_name);
0294: }
0295: if (!(restriction_object instanceof Number)) {
0296: throw new InvalidOrdinalRestrictionTypeException(
0297: bean.getClass(), propertyName,
0298: restriction_name, restriction_object
0299: .getClass());
0300: }
0301: restriction = ((Number) restriction_object).longValue();
0302: } catch (BeanUtilsException e) {
0303: throw new UnknownOrdinalRestrictionException(bean
0304: .getClass(), propertyName, restriction_name);
0305: }
0306:
0307: // obtain a new ordinal, taking the restriction value into account
0308: return ordinals.move(direction, restriction, ordinal);
0309: } else {
0310: ordinals = new OrdinalManager(getDatasource(), getTable(),
0311: propertyName);
0312: return ordinals.move(direction, ordinal);
0313: }
0314: }
0315:
0316: /**
0317: * Moves the row that corresponds to the provided bean instance upwards
0318: * according to a property with an ordinal constraint.
0319: *
0320: * @param bean the bean instance that corresponds to the row that has to
0321: * be moved
0322: * @param propertyName the name of the property with an ordinal constraint
0323: * @return <code>true</code> if the row was moved successfully; or
0324: * <p><code>false</code> otherwise
0325: * @since 1.0
0326: */
0327: public boolean up(Constrained bean, String propertyName) {
0328: return move(bean, propertyName, OrdinalManager.UP);
0329: }
0330:
0331: /**
0332: * Moves the row that corresponds to the provided bean instance downwards
0333: * according to a property with an ordinal constraint.
0334: *
0335: * @param bean the bean instance that corresponds to the row that has to
0336: * be moved
0337: * @param propertyName the name of the property with an ordinal constraint
0338: * @return <code>true</code> if the row was moved successfully; or
0339: * <p><code>false</code> otherwise
0340: * @since 1.0
0341: */
0342: public boolean down(Constrained bean, String propertyName) {
0343: return move(bean, propertyName, OrdinalManager.DOWN);
0344: }
0345:
0346: /**
0347: * Empties the content of a certain bean property.
0348: * <p>When a bean is saved, <code>null</code> content properties are
0349: * simply ignored when the property hasn't got an <code>autoRetrieved</code>
0350: * constraint. This is needed to make it possible to only update a
0351: * bean's data without having to fetch the content from the back-end and
0352: * store it together with the other data just to make a simple update.
0353: * However, this makes it impossible to rely on <code>null</code> to
0354: * indicate empty content. This method has thus been added explicitly for
0355: * this purpose.
0356: *
0357: * @param bean the bean instance that contains the property
0358: * @param propertyName the name of the property whose content has to be
0359: * emptied in the database
0360: * @return <code>true</code> if the empty content was stored successfully;
0361: * or
0362: * <p><code>false</code> otherwise
0363: * @since 1.0
0364: */
0365: public boolean storeEmptyContent(final T bean, String propertyName) {
0366: if (null == bean)
0367: throw new IllegalArgumentException(
0368: "constrained can't be null");
0369: if (null == propertyName)
0370: throw new IllegalArgumentException(
0371: "propertyName can't be null");
0372: if (0 == propertyName.length())
0373: throw new IllegalArgumentException(
0374: "propertyName can't be empty");
0375:
0376: Constrained constrained = ConstrainedUtils
0377: .makeConstrainedInstance(bean);
0378: if (null == constrained) {
0379: return false;
0380: }
0381:
0382: int id = getIdentifierValue(bean);
0383: if (-1 == id) {
0384: throw new MissingIdentifierValueException(bean.getClass(),
0385: getIdentifierName());
0386: }
0387:
0388: ConstrainedProperty property = constrained
0389: .getConstrainedProperty(propertyName);
0390: if (null == property) {
0391: throw new UnknownConstrainedPropertyException(bean
0392: .getClass(), propertyName);
0393: }
0394:
0395: if (!property.hasMimeType()) {
0396: throw new ExpectedMimeTypeConstraintException(bean
0397: .getClass(), propertyName);
0398: }
0399:
0400: try {
0401: Content content = new Content(property.getMimeType(), null)
0402: .fragment(property.isFragment()).name(
0403: property.getName()).attributes(
0404: property.getContentAttributes())
0405: .cachedLoadedData(property.getCachedLoadedData());
0406:
0407: return mContentManager.storeContent(buildCmfPath(
0408: constrained, id, property.getPropertyName()),
0409: content, property.getTransformer());
0410: } catch (ContentManagerException e) {
0411: throw new DatabaseException(e);
0412: }
0413: }
0414:
0415: /**
0416: * Saves a bean.
0417: * <p>This augments the regular <code>GenericQueryManager</code>'s
0418: * <code>save</code> method with behaviour that correctly handles content
0419: * or ordinal properties.
0420: * When a bean is saved, <code>null</code> content properties are simply
0421: * ignored when the property hasn't got an <code>autoRetrieved</code>
0422: * constraint. This is needed to make it possible to only update a bean's
0423: * data without having to fetch the content from the back-end and store it
0424: * together with the other data just to make a simple update.
0425: *
0426: * @param bean the bean instance that has to be saved
0427: * @return <code>true</code> if the bean was stored successfully; or
0428: * <p><code>false</code> otherwise
0429: * @since 1.0
0430: */
0431: public int save(final T bean) throws DatabaseException {
0432: return (Integer) mDbQueryManager
0433: .inTransaction(new DbTransactionUser() {
0434: public Integer useTransaction()
0435: throws InnerClassException {
0436: int id = -1;
0437:
0438: // determine if it's an update or an insert
0439: boolean update = false;
0440: id = getIdentifierValue(bean);
0441: if (id >= 0) {
0442: update = true;
0443: }
0444:
0445: // handle the pre-storage constraints logic
0446: Constrained constrained = ConstrainedUtils
0447: .makeConstrainedInstance(bean);
0448: Collection<ConstrainedProperty> properties = null;
0449: if (constrained != null) {
0450: properties = constrained
0451: .getConstrainedProperties();
0452:
0453: for (ConstrainedProperty property : properties) {
0454: if (!update) {
0455: if (property.isOrdinal()) {
0456: try {
0457: OrdinalManager ordinals = null;
0458:
0459: int new_ordinal = -1;
0460:
0461: if (property
0462: .hasOrdinalRestriction()) {
0463: String restriction_name = property
0464: .getOrdinalRestriction();
0465:
0466: // initialize the ordinal manager, taking the restriction property into account
0467: ordinals = new OrdinalManager(
0468: getDatasource(),
0469: getTable(),
0470: property
0471: .getPropertyName(),
0472: restriction_name);
0473:
0474: // obtain the restriction value
0475: long restriction = -1;
0476: try {
0477: Object restriction_object = BeanUtils
0478: .getPropertyValue(
0479: bean,
0480: restriction_name);
0481: if (null == restriction_object) {
0482: throw new OrdinalRestrictionCantBeNullException(
0483: bean
0484: .getClass(),
0485: property
0486: .getPropertyName(),
0487: restriction_name);
0488: }
0489: if (!(restriction_object instanceof Number)) {
0490: throw new InvalidOrdinalRestrictionTypeException(
0491: bean
0492: .getClass(),
0493: property
0494: .getPropertyName(),
0495: restriction_name,
0496: restriction_object
0497: .getClass());
0498: }
0499: restriction = ((Number) restriction_object)
0500: .longValue();
0501: } catch (BeanUtilsException e) {
0502: throw new UnknownOrdinalRestrictionException(
0503: bean
0504: .getClass(),
0505: property
0506: .getPropertyName(),
0507: restriction_name);
0508: }
0509:
0510: // obtain a new ordinal, taking the restriction value into account
0511: new_ordinal = ordinals
0512: .obtainInsertOrdinal(restriction);
0513: } else {
0514: ordinals = new OrdinalManager(
0515: getDatasource(),
0516: getTable(),
0517: property
0518: .getPropertyName());
0519: new_ordinal = ordinals
0520: .obtainInsertOrdinal();
0521: }
0522: BeanUtils
0523: .setPropertyValue(
0524: bean,
0525: property
0526: .getPropertyName(),
0527: new_ordinal);
0528: } catch (BeanUtilsException e) {
0529: throw new DatabaseException(
0530: e);
0531: }
0532: }
0533: }
0534: }
0535: }
0536:
0537: // store the new bean or update it
0538: id = ContentQueryManager.super .save(bean);
0539:
0540: return id;
0541: }
0542: });
0543: }
0544:
0545: /**
0546: * Restores a bean according to its ID.
0547: * <p>This augments the regular <code>GenericQueryManager</code>'s
0548: * <code>restore</code> method with behaviour that correctly handles
0549: * content properties.
0550: *
0551: * @param objectId the ID of the bean that has to be restored
0552: * @return the bean instance if it was restored successfully; or
0553: * <p><code>null</code> if it couldn't be found
0554: * @since 1.0
0555: */
0556: public T restore(int objectId) throws DatabaseException {
0557: return super .restore(objectId);
0558: }
0559:
0560: /**
0561: * Restores the first bean from a <code>RestoreQuery</code>.
0562: * <p>This augments the regular <code>GenericQueryManager</code>'s
0563: * <code>restore</code> method with behaviour that correctly handles
0564: * content properties.
0565: *
0566: * @param query the query that will be used to restore the beans
0567: * @return the first bean instance that was found; or
0568: * <p><code>null</code> if no beans could be found
0569: * @since 1.0
0570: */
0571: public T restoreFirst(RestoreQuery query) throws DatabaseException {
0572: return super .restoreFirst(query);
0573: }
0574:
0575: /**
0576: * Restores all beans.
0577: * <p>This augments the regular <code>GenericQueryManager</code>'s
0578: * <code>restore</code> method with behaviour that correctly handles
0579: * content properties.
0580: *
0581: * @return the list of beans; or
0582: * <p><code>null</code> if no beans could be found
0583: * @since 1.0
0584: */
0585: public List<T> restore() throws DatabaseException {
0586: return super .restore();
0587: }
0588:
0589: /**
0590: * Restores all beans from a <code>RestoreQuery</code>.
0591: * <p>This augments the regular <code>GenericQueryManager</code>'s
0592: * <code>restore</code> method with behaviour that correctly handles
0593: * content properties.
0594: *
0595: * @param query the query that will be used to restore the beans
0596: * @return the list of beans; or
0597: * <p><code>null</code> if no beans could be found
0598: * @since 1.0
0599: */
0600: public List<T> restore(RestoreQuery query) throws DatabaseException {
0601: return super .restore(query);
0602: }
0603:
0604: private void restoreContent(int objectId, final T bean) {
0605: Constrained constrained = ConstrainedUtils
0606: .makeConstrainedInstance(bean);
0607: if (constrained != null) {
0608: Collection<ConstrainedProperty> properties = constrained
0609: .getConstrainedProperties();
0610: for (ConstrainedProperty property : properties) {
0611: if (property.hasMimeType()
0612: && property.isAutoRetrieved()) {
0613: mContentManager
0614: .useContentData(
0615: buildCmfPath(constrained, objectId,
0616: property.getPropertyName()),
0617: new ContentDataUser<Object, ConstrainedProperty>(
0618: property) {
0619: public Object useContentData(
0620: Object contentData)
0621: throws InnerClassException {
0622: try {
0623: BeanUtils
0624: .setPropertyValue(
0625: bean,
0626: getData()
0627: .getPropertyName(),
0628: contentData);
0629: } catch (BeanUtilsException e) {
0630: throw new DatabaseException(
0631: e);
0632: } catch (ContentManagerException e) {
0633: throw new DatabaseException(
0634: e);
0635: }
0636: return null;
0637: }
0638: });
0639: }
0640: }
0641: }
0642: }
0643:
0644: /**
0645: * Deletes a bean according to its ID.
0646: * <p>This augments the regular <code>GenericQueryManager</code>'s
0647: * <code>restore</code> method with behaviour that correctly handles
0648: * content and ordinal properties.
0649: *
0650: * @param objectId the ID of the bean that has to be restored
0651: * @return <code>true</code> if the bean was deleted successfully; or
0652: * <p><code>false</code> if it couldn't be found
0653: * @since 1.0
0654: */
0655: public boolean delete(final int objectId) throws DatabaseException {
0656: Boolean result = mDbQueryManager
0657: .inTransaction(new DbTransactionUser() {
0658: public Boolean useTransaction()
0659: throws InnerClassException {
0660: T bean = restore(objectId);
0661: if (null == bean) {
0662: return false;
0663: }
0664:
0665: mDeletedbean.set(bean);
0666: try {
0667: if (ContentQueryManager.super
0668: .delete(objectId)) {
0669: return true;
0670: }
0671: } finally {
0672: mDeletedbean.set(null);
0673: }
0674:
0675: return false;
0676: }
0677: });
0678:
0679: return null != result && result.booleanValue();
0680: }
0681:
0682: /**
0683: * Checks if there's content available for a certain property of a bean.
0684: *
0685: * @param bean the bean instance that will be checked
0686: * @param propertyName the name of the property whose content availability
0687: * will be checked
0688: * @return <code>true</code> if content is available; or
0689: * <p><code>false</code> otherwise
0690: * @since 1.0
0691: */
0692: public boolean hasContent(T bean, String propertyName)
0693: throws DatabaseException {
0694: Constrained constrained = ConstrainedUtils
0695: .makeConstrainedInstance(bean);
0696:
0697: return hasContent(constrained, getIdentifierValue(bean),
0698: propertyName);
0699: }
0700:
0701: /**
0702: * Checks if there's content available for a certain property of a bean.
0703: *
0704: * @param objectId the ID of the bean instance that will be checked
0705: * @param propertyName the name of the property whose content availability
0706: * will be checked
0707: * @return <code>true</code> if content is available; or
0708: * <p><code>false</code> otherwise
0709: * @since 1.0
0710: */
0711: public boolean hasContent(int objectId, String propertyName)
0712: throws DatabaseException {
0713: Constrained constrained = ConstrainedUtils
0714: .getConstrainedInstance(mClass);
0715:
0716: return hasContent(constrained, objectId, propertyName);
0717: }
0718:
0719: private boolean hasContent(Constrained constrained, int objectId,
0720: String propertyName) throws DatabaseException {
0721: try {
0722: return mContentManager.hasContentData(buildCmfPath(
0723: constrained, objectId, propertyName));
0724: } catch (ContentManagerException e) {
0725: throw new DatabaseException(e);
0726: }
0727: }
0728:
0729: /**
0730: * Builds the path that is used by the <code>ContentQueryManager</code>
0731: * for a certain bean and property.
0732: *
0733: * @param bean the bean instance that will be used to construct the path
0734: * @param propertyName the name of the property that will be used to
0735: * construct the path
0736: * @return the requested path
0737: * @since 1.0
0738: */
0739: public String buildCmfPath(T bean, String propertyName) {
0740: Constrained constrained = ConstrainedUtils
0741: .makeConstrainedInstance(bean);
0742:
0743: return buildCmfPath(constrained, getIdentifierValue(bean),
0744: propertyName, true);
0745: }
0746:
0747: /**
0748: * Builds the path that is used by the <code>ContentQueryManager</code>
0749: * for a certain bean ID and property.
0750: *
0751: * @param objectId the bean ID that will be used to construct the path
0752: * @param propertyName the name of the property that will be used to
0753: * construct the path
0754: * @return the requested path
0755: * @since 1.0
0756: */
0757: public String buildCmfPath(int objectId, String propertyName) {
0758: Constrained constrained = ConstrainedUtils
0759: .getConstrainedInstance(mClass);
0760:
0761: return buildCmfPath(constrained, objectId, propertyName, true);
0762: }
0763:
0764: /**
0765: * Builds the path that is used by the <code>ServeContent</code> element
0766: * for a certain bean and property.
0767: * <p>Any declaration of the repository name will be ignore, since the
0768: * <code>ServeContent</code> element doesn't allow you to provide this
0769: * through the URL for safety reasons.
0770: *
0771: * @param bean the bean instance that will be used to construct the path
0772: * @param propertyName the name of the property that will be used to
0773: * construct the path
0774: * @return the requested path
0775: * @since 1.4
0776: */
0777: public String buildServeContentPath(T bean, String propertyName) {
0778: Constrained constrained = ConstrainedUtils
0779: .makeConstrainedInstance(bean);
0780:
0781: return buildCmfPath(constrained, getIdentifierValue(bean),
0782: propertyName, false);
0783: }
0784:
0785: /**
0786: * Builds the path that is used by the <code>ServeContent</code> element
0787: * for a certain bean ID and property.
0788: * <p>Any declaration of the repository name will be ignore, since the
0789: * <code>ServeContent</code> element doesn't allow you to provide this
0790: * through the URL for safety reasons.
0791: *
0792: * @param objectId the bean ID that will be used to construct the path
0793: * @param propertyName the name of the property that will be used to
0794: * construct the path
0795: * @return the requested path
0796: * @since 1.4
0797: */
0798: public String buildServeContentPath(int objectId,
0799: String propertyName) {
0800: Constrained constrained = ConstrainedUtils
0801: .getConstrainedInstance(mClass);
0802:
0803: return buildCmfPath(constrained, objectId, propertyName, false);
0804: }
0805:
0806: private String buildCmfPath(Constrained constrained, int objectId,
0807: String propertyName) {
0808: return buildCmfPath(constrained, objectId, propertyName, true);
0809: }
0810:
0811: private String buildCmfPath(Constrained constrained, int objectId,
0812: String propertyName, boolean useRepository) {
0813: String repository = null;
0814: if (useRepository) {
0815: if (mRepository != null) {
0816: repository = mRepository;
0817: }
0818: if (constrained != null) {
0819: ConstrainedProperty property = constrained
0820: .getConstrainedProperty(propertyName);
0821: if (property != null && property.hasRepository()) {
0822: repository = property.getRepository();
0823: }
0824: }
0825: }
0826:
0827: StringBuilder path = new StringBuilder("");
0828: if (repository != null && repository.length() > 0) {
0829: path.append(repository);
0830: path.append(":");
0831: }
0832: path.append("/");
0833: String classname = mBackendClass.getName();
0834: classname = classname.substring(classname.lastIndexOf(".") + 1);
0835: path.append(StringUtils.encodeUrl(classname));
0836: path.append("/");
0837: path.append(objectId);
0838: path.append("/");
0839: path.append(StringUtils.encodeUrl(propertyName));
0840:
0841: return path.toString().toLowerCase();
0842: }
0843:
0844: /**
0845: * Retrieves a content data representation for use in html.
0846: * <p>This is mainly used to integrate content data inside a html
0847: * document. For instance, html content will be displayed as-is, while
0848: * image content will cause an image tag to be generated with the correct
0849: * source URL to serve the image.
0850: *
0851: * @param bean the bean instance that contains the data
0852: * @param propertyName the name of the property whose html representation
0853: * will be provided
0854: * @param element an active element instance
0855: * @param serveContentExitName the exit name that leads to a {@link
0856: * com.uwyn.rife.cmf.elements.ServeContent ServeContent} element. This will
0857: * be used to generate URLs for content that can't be directly displayed
0858: * in-line.
0859: * @return the html content representation
0860: * @exception ContentManagerException if an unexpected error occurred
0861: * @since 1.0
0862: */
0863: public String getContentForHtml(T bean, String propertyName,
0864: Element element, String serveContentExitName)
0865: throws ContentManagerException {
0866: return getContentManager().getContentForHtml(
0867: buildCmfPath(bean, propertyName), element,
0868: serveContentExitName);
0869: }
0870:
0871: /**
0872: * Retrieves a content data representation for use in html.
0873: * <p>This is mainly used to integrate content data inside a html
0874: * document. For instance, html content will be displayed as-is, while
0875: * image content will cause an image tag to be generated with the correct
0876: * source URL to serve the image.
0877: *
0878: * @param objectId the ID of the bean that contains the data
0879: * @param propertyName the name of the property whose html representation
0880: * will be provided
0881: * @param element an active element instance
0882: * @param serveContentExitName the exit name that leads to a {@link
0883: * com.uwyn.rife.cmf.elements.ServeContent ServeContent} element. This will
0884: * be used to generate URLs for content that can't be directly displayed
0885: * in-line.
0886: * @return the html content representation
0887: * @exception ContentManagerException if an unexpected error occurred
0888: * @since 1.0
0889: */
0890: public String getContentForHtml(int objectId, String propertyName,
0891: Element element, String serveContentExitName)
0892: throws ContentManagerException {
0893: return getContentManager().getContentForHtml(
0894: buildCmfPath(objectId, propertyName), element,
0895: serveContentExitName);
0896: }
0897:
0898: /**
0899: * Simply clones the instance with the default clone method. This creates
0900: * a shallow copy of all fields and the clone will in fact just be another
0901: * reference to the same underlying data. The independence of each cloned
0902: * instance is consciously not respected since they rely on resources that
0903: * can't be cloned.
0904: *
0905: * @since 1.0
0906: */
0907: public Object clone() {
0908: try {
0909: return super .clone();
0910: } catch (CloneNotSupportedException e) {
0911: ///CLOVER:OFF
0912: // this should never happen
0913: Logger.getLogger("com.uwyn.rife.cmf").severe(
0914: ExceptionUtils.getExceptionStackTrace(e));
0915: return null;
0916: ///CLOVER:ON
0917: }
0918: }
0919:
0920: class Listener implements GenericQueryManagerListener<T> {
0921: public void installed() {
0922: }
0923:
0924: public void removed() {
0925: }
0926:
0927: public void inserted(T bean) {
0928: saved(bean);
0929: }
0930:
0931: public void updated(T bean) {
0932: saved(bean);
0933: }
0934:
0935: public void saved(T bean) {
0936: Constrained constrained = ConstrainedUtils
0937: .makeConstrainedInstance(bean);
0938: Collection<ConstrainedProperty> properties = null;
0939: if (constrained != null) {
0940: properties = constrained.getConstrainedProperties();
0941: }
0942:
0943: // process the properties that have to be handled after the saving of the bean
0944: if (properties != null) {
0945: int id = getIdentifierValue(bean);
0946:
0947: for (ConstrainedProperty property : properties) {
0948: if (property.hasMimeType()) {
0949: try {
0950: Object value = BeanUtils.getPropertyValue(
0951: bean, property.getPropertyName());
0952: if (value != null
0953: || property.isAutoRetrieved()) {
0954: Content content = new Content(property
0955: .getMimeType(), value)
0956: .fragment(property.isFragment())
0957: .name(property.getName())
0958: .attributes(
0959: property
0960: .getContentAttributes())
0961: .cachedLoadedData(
0962: property
0963: .getCachedLoadedData());
0964:
0965: mContentManager
0966: .storeContent(
0967: buildCmfPath(
0968: constrained,
0969: id,
0970: property
0971: .getPropertyName()),
0972: content,
0973: property
0974: .getTransformer());
0975: }
0976: } catch (BeanUtilsException e) {
0977: throw new DatabaseException(e);
0978: } catch (ContentManagerException e) {
0979: throw new DatabaseException(e);
0980: }
0981: }
0982: }
0983: }
0984: }
0985:
0986: public void restored(T bean) {
0987: restoreContent(getIdentifierValue(bean), bean);
0988: }
0989:
0990: public void deleted(int objectId) {
0991: T bean = mDeletedbean.get();
0992: if (null == bean) {
0993: return;
0994: }
0995:
0996: Constrained constrained = ConstrainedUtils
0997: .makeConstrainedInstance(bean);
0998: if (constrained != null) {
0999: Collection<ConstrainedProperty> properties = constrained
1000: .getConstrainedProperties();
1001: for (ConstrainedProperty property : properties) {
1002: if (property.hasMimeType()) {
1003: mContentManager.deleteContent(buildCmfPath(
1004: constrained, objectId, property
1005: .getPropertyName()));
1006: } else if (property.isOrdinal()) {
1007: OrdinalManager ordinals = null;
1008: if (property.hasOrdinalRestriction()) {
1009: String restriction_name = property
1010: .getOrdinalRestriction();
1011:
1012: // initialize the ordinal manager, taking the restriction property into account
1013: ordinals = new OrdinalManager(
1014: getDatasource(), getTable(),
1015: property.getPropertyName(),
1016: restriction_name);
1017:
1018: // obtain the restriction value
1019: long restriction = -1;
1020: try {
1021: Object restriction_object = BeanUtils
1022: .getPropertyValue(bean,
1023: restriction_name);
1024: if (null == restriction_object) {
1025: throw new OrdinalRestrictionCantBeNullException(
1026: bean.getClass(), property
1027: .getPropertyName(),
1028: restriction_name);
1029: }
1030: if (!(restriction_object instanceof Number)) {
1031: throw new InvalidOrdinalRestrictionTypeException(
1032: bean.getClass(), property
1033: .getPropertyName(),
1034: restriction_name,
1035: restriction_object
1036: .getClass());
1037: }
1038: restriction = ((Number) restriction_object)
1039: .longValue();
1040: } catch (BeanUtilsException e) {
1041: throw new UnknownOrdinalRestrictionException(
1042: bean.getClass(), property
1043: .getPropertyName(),
1044: restriction_name);
1045: }
1046:
1047: // tighten the remaining ordinals, taking the restriction value into account
1048: ordinals.tighten(restriction);
1049: } else {
1050: ordinals = new OrdinalManager(
1051: getDatasource(), getTable(),
1052: property.getPropertyName());
1053: ordinals.tighten();
1054: }
1055: }
1056: }
1057: }
1058: }
1059: }
1060:
1061: public <OtherBeanType> GenericQueryManager<OtherBeanType> createNewManager(
1062: Class<OtherBeanType> type) {
1063: return new ContentQueryManager(getDatasource(), type,
1064: mContentManager);
1065: }
1066: }
|