/* * Copyright 2001-2008 Geert Bevin <gbevin[remove] at uwyn dot com> * Licensed under the Apache License, Version 2.0 (the "License") * $Id: ContentQueryManager.java 3918 2008-04-14 17:35:35Z gbevin $ */ package com.uwyn.rife.cmf.dam; import com.uwyn.rife.cmf.dam.exceptions.*; import com.uwyn.rife.cmf.Content; import com.uwyn.rife.cmf.dam.contentmanagers.DatabaseContentFactory; import com.uwyn.rife.database.Datasource; import com.uwyn.rife.database.DbQueryManager; import com.uwyn.rife.database.DbTransactionUser; import com.uwyn.rife.database.exceptions.DatabaseException; import com.uwyn.rife.database.querymanagers.generic.GenericQueryManager; import com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerDelegate; import com.uwyn.rife.database.querymanagers.generic.GenericQueryManagerListener; import com.uwyn.rife.database.querymanagers.generic.RestoreQuery; import com.uwyn.rife.engine.Element; import com.uwyn.rife.site.Constrained; import com.uwyn.rife.site.ConstrainedProperty; import com.uwyn.rife.site.ConstrainedUtils; import com.uwyn.rife.tools.BeanUtils; import com.uwyn.rife.tools.ClassUtils; import com.uwyn.rife.tools.ExceptionUtils; import com.uwyn.rife.tools.exceptions.InnerClassException; import com.uwyn.rife.tools.StringUtils; import com.uwyn.rife.tools.exceptions.BeanUtilsException; import java.util.Collection; import java.util.List; import java.util.logging.Logger; /** * The <code>ContentQueryManager</code> simplifies working with content a lot. * It extends {@link * com.uwyn.rife.database.querymanagers.generic.GenericQueryManager * GenericQueryManager} and is a drop-in replacement that can be used instead. * The <code>ContentQueryManager</code> class works hand-in-hand with * CMF-related constraints that are provided via the classes {@link * com.uwyn.rife.site.Validation Validation} and {@link * com.uwyn.rife.site.ConstrainedProperty ConstrainedProperty}. The additional constraints * allow you to provide CMF-related metadata for bean properties while still * having access to all regular constraints. * <p>The most important additional constraint is '{@link * com.uwyn.rife.site.ConstrainedProperty#mimeType(com.uwyn.rife.cmf.MimeType) mimeType}'. Setting this * constraint directs RIFE to delegate the handling of that property's data to * the CMF instead of storing it as a regular column in a database table. The * property content location (i.e. its full path) is generated automatically * based on the bean class name, the instance's identifier value (i.e. the * primary key used by <code>GenericQueryManager</code>), and the property * name. So for example, if you have an instance of the <code>NewsItem</code> * class whose identifier is <code>23</code>, then the full path that is * generated for a property named <code>text</code> is '<code>/newsitem/23/text</code>'. * Note that this always specifies the most recent version of the property, * but that older versions are also available from the content store. * <p>Before being able to use the CMF and a <code>ContentQueryManager</code>, * you must install both of them, as in this example: * <pre>Datasource ds = Datasources.getRepInstance().getDatasource("datasource"); *DatabaseContentFactory.getInstance(ds).install(); *new ContentQueryManager(ds, NewsItem.class).install();</pre> * <p>Apart from the handling of content, this query manager also integrates * the functionalities of the {@link OrdinalManager} class. * <p>The new '{@link com.uwyn.rife.site.ConstrainedProperty#ordinal(boolean) ordinal}' * constraint indicates which bean property will be used to order that table * rows. When saving and deleting beans, the ordinal values will be * automatically updated in the entire table. The * <code>ContentQueryManager</code> also provides the {@link * #move(Constrained, String, OrdinalManager.Direction) move}, {@link * #up(Constrained, String) up} and {@link #down(Constrained, String) down} * methods to easily manipulate the order of existing rows. * * @author Geert Bevin (gbevin[remove] at uwyn dot com)i * @version $Revision: 3918 $ * @since 1.0 */ public class ContentQueryManager<T> extends GenericQueryManagerDelegate<T> implements Cloneable { private Class mClass = null; private Class mBackendClass = null; private DbQueryManager mDbQueryManager = null; private ContentManager mContentManager = null; private String mRepository = null; private ThreadLocal<T> mDeletedbean = new ThreadLocal<T>(); /** * Creates a new <code>ContentQueryManager</code> instance for a specific * class. * <p>All content will be stored in a {@link * com.uwyn.rife.cmf.dam.contentmanagers.DatabaseContent}. * * @param datasource the datasource that indicates where the data will be * stored * @param klass the class of the bean that will be handled by this * <code>ContentQueryManager</code> * @param backendClass the class the will be used by this * <code>ContentQueryManager</code> to reference data in the backend * @since 1.0 */ public ContentQueryManager(Datasource datasource, Class<T> klass, Class backendClass) { super(datasource, klass, ClassUtils.shortenClassName(backendClass)); mClass = klass; mBackendClass = backendClass; mDbQueryManager = new DbQueryManager(datasource); mContentManager = DatabaseContentFactory.getInstance(datasource); addListener(new Listener()); } /** * Creates a new <code>ContentQueryManager</code> instance for a specific * class, but with a different table name for the database storage. * <p>All content will be stored in a {@link * com.uwyn.rife.cmf.dam.contentmanagers.DatabaseContent}. * * @param datasource the datasource that indicates where the data will be * stored * @param klass the class of the bean that will be handled by this * <code>ContentQueryManager</code> * @param table the name of the database table in which the non CMF data will * be stored * @since 1.6 */ public ContentQueryManager(Datasource datasource, Class<T> klass, String table) { super(datasource, klass, table); mClass = klass; mBackendClass = klass; mDbQueryManager = new DbQueryManager(datasource); mContentManager = DatabaseContentFactory.getInstance(datasource); addListener(new Listener()); } /** * Creates a new <code>ContentQueryManager</code> instance for a specific * class. * <p>All content will be stored in a {@link * com.uwyn.rife.cmf.dam.contentmanagers.DatabaseContent}. * * @param datasource the datasource that indicates where the data will be * stored * @param klass the class of the bean that will be handled by this * <code>ContentQueryManager</code> * @since 1.0 */ public ContentQueryManager(Datasource datasource, Class<T> klass) { super(datasource, klass); mClass = klass; mBackendClass = klass; mDbQueryManager = new DbQueryManager(datasource); mContentManager = DatabaseContentFactory.getInstance(datasource); addListener(new Listener()); } /** * Creates a new <code>ContentQueryManager</code> instance for a specific * class. * <p>All content will be stored in the provided * <code>ContentManager</code> instance. This constructor is handy if you * want to integrate a custom content manager implementation. * * @param datasource the datasource that indicates where the data will be * stored * @param klass the class of the bean that will be handled by this * <code>ContentQueryManager</code> * @param contentManager a <code>ContentManager</code> instance * @since 1.0 */ public ContentQueryManager(Datasource datasource, Class<T> klass, ContentManager contentManager) { super(datasource, klass); mClass = klass; mContentManager = contentManager; addListener(new Listener()); } /** * Sets the default repository that will be used by this <code>ContentQueryManager</code>. * * @return this <code>ContentQueryManager</code> * @see #getRepository * @since 1.4 */ public ContentQueryManager<T> repository(String repository) { mRepository = repository; return this; } /** * Retrieves the default repository that is used by this <code>ContentQueryManager</code>. * * @return this <code>ContentQueryManager</code>'s repository * @see #repository * @since 1.4 */ public String getRepository() { return mRepository; } /** * Returns the <code>ContentManager</code> that is used to store and * retrieve the content. * * @return the <code>ContentManager</code> * @since 1.0 */ public ContentManager getContentManager() { return mContentManager; } /** * Moves the row that corresponds to the provided bean instance according * to a property with an ordinal constraint. * * @param bean the bean instance that corresponds to the row that has to * be moved * @param propertyName the name of the property with an ordinal constraint * @param direction {@link OrdinalManager#UP} or {@link * OrdinalManager#DOWN} * @return <code>true</code> if the row was moved successfully; or * <p><code>false</code> otherwise * @since 1.0 */ public boolean move(Constrained bean, String propertyName, OrdinalManager.Direction direction) { if (null == bean) throw new IllegalArgumentException("constrained can't be nul"); if (null == propertyName) throw new IllegalArgumentException("propertyName can't be null"); if (0 == propertyName.length()) throw new IllegalArgumentException("propertyName can't be empty"); ConstrainedProperty property = bean.getConstrainedProperty(propertyName); if (null == property) { throw new UnknownConstrainedPropertyException(bean.getClass(), propertyName); } if (!property.isOrdinal()) { throw new ExpectedOrdinalConstraintException(bean.getClass(), propertyName); } // obtain the ordinal value int ordinal = -1; try { Object ordinal_object = BeanUtils.getPropertyValue(bean, propertyName); if (!(ordinal_object instanceof Integer)) { throw new InvalidOrdinalTypeException(bean.getClass(), propertyName); } ordinal = ((Integer)ordinal_object).intValue(); } catch (BeanUtilsException e) { throw new UnknownOrdinalException(bean.getClass(), propertyName); } OrdinalManager ordinals = null; if (property.hasOrdinalRestriction()) { String restriction_name = property.getOrdinalRestriction(); // initialy the ordinal manager, taking the restriction property into account ordinals = new OrdinalManager(getDatasource(), getTable(), propertyName, restriction_name); // obtain the restriction value long restriction = -1; try { Object restriction_object = BeanUtils.getPropertyValue(bean, restriction_name); if (null == restriction_object) { throw new OrdinalRestrictionCantBeNullException(bean.getClass(), property.getPropertyName(), restriction_name); } if (!(restriction_object instanceof Number)) { throw new InvalidOrdinalRestrictionTypeException(bean.getClass(), propertyName, restriction_name, restriction_object.getClass()); } restriction = ((Number)restriction_object).longValue(); } catch (BeanUtilsException e) { throw new UnknownOrdinalRestrictionException(bean.getClass(), propertyName, restriction_name); } // obtain a new ordinal, taking the restriction value into account return ordinals.move(direction, restriction, ordinal); } else { ordinals = new OrdinalManager(getDatasource(), getTable(), propertyName); return ordinals.move(direction, ordinal); } } /** * Moves the row that corresponds to the provided bean instance upwards * according to a property with an ordinal constraint. * * @param bean the bean instance that corresponds to the row that has to * be moved * @param propertyName the name of the property with an ordinal constraint * @return <code>true</code> if the row was moved successfully; or * <p><code>false</code> otherwise * @since 1.0 */ public boolean up(Constrained bean, String propertyName) { return move(bean, propertyName, OrdinalManager.UP); } /** * Moves the row that corresponds to the provided bean instance downwards * according to a property with an ordinal constraint. * * @param bean the bean instance that corresponds to the row that has to * be moved * @param propertyName the name of the property with an ordinal constraint * @return <code>true</code> if the row was moved successfully; or * <p><code>false</code> otherwise * @since 1.0 */ public boolean down(Constrained bean, String propertyName) { return move(bean, propertyName, OrdinalManager.DOWN); } /** * Empties the content of a certain bean property. * <p>When a bean is saved, <code>null</code> content properties are * simply ignored when the property hasn't got an <code>autoRetrieved</code> * constraint. This is needed to make it possible to only update a * bean's data without having to fetch the content from the back-end and * store it together with the other data just to make a simple update. * However, this makes it impossible to rely on <code>null</code> to * indicate empty content. This method has thus been added explicitly for * this purpose. * * @param bean the bean instance that contains the property * @param propertyName the name of the property whose content has to be * emptied in the database * @return <code>true</code> if the empty content was stored successfully; * or * <p><code>false</code> otherwise * @since 1.0 */ public boolean storeEmptyContent(final T bean, String propertyName) { if (null == bean) throw new IllegalArgumentException("constrained can't be null"); if (null == propertyName) throw new IllegalArgumentException("propertyName can't be null"); if (0 == propertyName.length()) throw new IllegalArgumentException("propertyName can't be empty"); Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean); if (null == constrained) { return false; } int id = getIdentifierValue(bean); if (-1 == id) { throw new MissingIdentifierValueException(bean.getClass(), getIdentifierName()); } ConstrainedProperty property = constrained.getConstrainedProperty(propertyName); if (null == property) { throw new UnknownConstrainedPropertyException(bean.getClass(), propertyName); } if (!property.hasMimeType()) { throw new ExpectedMimeTypeConstraintException(bean.getClass(), propertyName); } try { Content content = new Content(property.getMimeType(), null) .fragment(property.isFragment()) .name(property.getName()) .attributes(property.getContentAttributes()) .cachedLoadedData(property.getCachedLoadedData()); return mContentManager.storeContent(buildCmfPath(constrained, id, property.getPropertyName()), content, property.getTransformer()); } catch (ContentManagerException e) { throw new DatabaseException(e); } } /** * Saves a bean. * <p>This augments the regular <code>GenericQueryManager</code>'s * <code>save</code> method with behaviour that correctly handles content * or ordinal properties. * When a bean is saved, <code>null</code> content properties are simply * ignored when the property hasn't got an <code>autoRetrieved</code> * constraint. This is needed to make it possible to only update a bean's * data without having to fetch the content from the back-end and store it * together with the other data just to make a simple update. * * @param bean the bean instance that has to be saved * @return <code>true</code> if the bean was stored successfully; or * <p><code>false</code> otherwise * @since 1.0 */ public int save(final T bean) throws DatabaseException { return (Integer)mDbQueryManager.inTransaction(new DbTransactionUser() { public Integer useTransaction() throws InnerClassException { int id = -1; // determine if it's an update or an insert boolean update = false; id = getIdentifierValue(bean); if (id >= 0) { update = true; } // handle the pre-storage constraints logic Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean); Collection<ConstrainedProperty> properties = null; if (constrained != null) { properties = constrained.getConstrainedProperties(); for (ConstrainedProperty property : properties) { if (!update) { if (property.isOrdinal()) { try { OrdinalManager ordinals = null; int new_ordinal = -1; if (property.hasOrdinalRestriction()) { String restriction_name = property.getOrdinalRestriction(); // initialize the ordinal manager, taking the restriction property into account ordinals = new OrdinalManager(getDatasource(), getTable(), property.getPropertyName(), restriction_name); // obtain the restriction value long restriction = -1; try { Object restriction_object = BeanUtils.getPropertyValue(bean, restriction_name); if (null == restriction_object) { throw new OrdinalRestrictionCantBeNullException(bean.getClass(), property.getPropertyName(), restriction_name); } if (!(restriction_object instanceof Number)) { throw new InvalidOrdinalRestrictionTypeException(bean.getClass(), property.getPropertyName(), restriction_name, restriction_object.getClass()); } restriction = ((Number)restriction_object).longValue(); } catch (BeanUtilsException e) { throw new UnknownOrdinalRestrictionException(bean.getClass(), property.getPropertyName(), restriction_name); } // obtain a new ordinal, taking the restriction value into account new_ordinal = ordinals.obtainInsertOrdinal(restriction); } else { ordinals = new OrdinalManager(getDatasource(), getTable(), property.getPropertyName()); new_ordinal = ordinals.obtainInsertOrdinal(); } BeanUtils.setPropertyValue(bean, property.getPropertyName(), new_ordinal); } catch (BeanUtilsException e) { throw new DatabaseException(e); } } } } } // store the new bean or update it id = ContentQueryManager.super.save(bean); return id; } }); } /** * Restores a bean according to its ID. * <p>This augments the regular <code>GenericQueryManager</code>'s * <code>restore</code> method with behaviour that correctly handles * content properties. * * @param objectId the ID of the bean that has to be restored * @return the bean instance if it was restored successfully; or * <p><code>null</code> if it couldn't be found * @since 1.0 */ public T restore(int objectId) throws DatabaseException { return super.restore(objectId); } /** * Restores the first bean from a <code>RestoreQuery</code>. * <p>This augments the regular <code>GenericQueryManager</code>'s * <code>restore</code> method with behaviour that correctly handles * content properties. * * @param query the query that will be used to restore the beans * @return the first bean instance that was found; or * <p><code>null</code> if no beans could be found * @since 1.0 */ public T restoreFirst(RestoreQuery query) throws DatabaseException { return super.restoreFirst(query); } /** * Restores all beans. * <p>This augments the regular <code>GenericQueryManager</code>'s * <code>restore</code> method with behaviour that correctly handles * content properties. * * @return the list of beans; or * <p><code>null</code> if no beans could be found * @since 1.0 */ public List<T> restore() throws DatabaseException { return super.restore(); } /** * Restores all beans from a <code>RestoreQuery</code>. * <p>This augments the regular <code>GenericQueryManager</code>'s * <code>restore</code> method with behaviour that correctly handles * content properties. * * @param query the query that will be used to restore the beans * @return the list of beans; or * <p><code>null</code> if no beans could be found * @since 1.0 */ public List<T> restore(RestoreQuery query) throws DatabaseException { return super.restore(query); } private void restoreContent(int objectId, final T bean) { Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean); if (constrained != null) { Collection<ConstrainedProperty> properties = constrained.getConstrainedProperties(); for (ConstrainedProperty property : properties) { if (property.hasMimeType() && property.isAutoRetrieved()) { mContentManager.useContentData(buildCmfPath(constrained, objectId, property.getPropertyName()), new ContentDataUser<Object, ConstrainedProperty>(property) { public Object useContentData(Object contentData) throws InnerClassException { try { BeanUtils.setPropertyValue(bean, getData().getPropertyName(), contentData); } catch (BeanUtilsException e) { throw new DatabaseException(e); } catch (ContentManagerException e) { throw new DatabaseException(e); } return null; } }); } } } } /** * Deletes a bean according to its ID. * <p>This augments the regular <code>GenericQueryManager</code>'s * <code>restore</code> method with behaviour that correctly handles * content and ordinal properties. * * @param objectId the ID of the bean that has to be restored * @return <code>true</code> if the bean was deleted successfully; or * <p><code>false</code> if it couldn't be found * @since 1.0 */ public boolean delete(final int objectId) throws DatabaseException { Boolean result = mDbQueryManager.inTransaction(new DbTransactionUser() { public Boolean useTransaction() throws InnerClassException { T bean = restore(objectId); if (null == bean) { return false; } mDeletedbean.set(bean); try { if (ContentQueryManager.super.delete(objectId)) { return true; } } finally { mDeletedbean.set(null); } return false; } }); return null != result && result.booleanValue(); } /** * Checks if there's content available for a certain property of a bean. * * @param bean the bean instance that will be checked * @param propertyName the name of the property whose content availability * will be checked * @return <code>true</code> if content is available; or * <p><code>false</code> otherwise * @since 1.0 */ public boolean hasContent(T bean, String propertyName) throws DatabaseException { Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean); return hasContent(constrained, getIdentifierValue(bean), propertyName); } /** * Checks if there's content available for a certain property of a bean. * * @param objectId the ID of the bean instance that will be checked * @param propertyName the name of the property whose content availability * will be checked * @return <code>true</code> if content is available; or * <p><code>false</code> otherwise * @since 1.0 */ public boolean hasContent(int objectId, String propertyName) throws DatabaseException { Constrained constrained = ConstrainedUtils.getConstrainedInstance(mClass); return hasContent(constrained, objectId, propertyName); } private boolean hasContent(Constrained constrained, int objectId, String propertyName) throws DatabaseException { try { return mContentManager.hasContentData(buildCmfPath(constrained, objectId, propertyName)); } catch (ContentManagerException e) { throw new DatabaseException(e); } } /** * Builds the path that is used by the <code>ContentQueryManager</code> * for a certain bean and property. * * @param bean the bean instance that will be used to construct the path * @param propertyName the name of the property that will be used to * construct the path * @return the requested path * @since 1.0 */ public String buildCmfPath(T bean, String propertyName) { Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean); return buildCmfPath(constrained, getIdentifierValue(bean), propertyName, true); } /** * Builds the path that is used by the <code>ContentQueryManager</code> * for a certain bean ID and property. * * @param objectId the bean ID that will be used to construct the path * @param propertyName the name of the property that will be used to * construct the path * @return the requested path * @since 1.0 */ public String buildCmfPath(int objectId, String propertyName) { Constrained constrained = ConstrainedUtils.getConstrainedInstance(mClass); return buildCmfPath(constrained, objectId, propertyName, true); } /** * Builds the path that is used by the <code>ServeContent</code> element * for a certain bean and property. * <p>Any declaration of the repository name will be ignore, since the * <code>ServeContent</code> element doesn't allow you to provide this * through the URL for safety reasons. * * @param bean the bean instance that will be used to construct the path * @param propertyName the name of the property that will be used to * construct the path * @return the requested path * @since 1.4 */ public String buildServeContentPath(T bean, String propertyName) { Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean); return buildCmfPath(constrained, getIdentifierValue(bean), propertyName, false); } /** * Builds the path that is used by the <code>ServeContent</code> element * for a certain bean ID and property. * <p>Any declaration of the repository name will be ignore, since the * <code>ServeContent</code> element doesn't allow you to provide this * through the URL for safety reasons. * * @param objectId the bean ID that will be used to construct the path * @param propertyName the name of the property that will be used to * construct the path * @return the requested path * @since 1.4 */ public String buildServeContentPath(int objectId, String propertyName) { Constrained constrained = ConstrainedUtils.getConstrainedInstance(mClass); return buildCmfPath(constrained, objectId, propertyName, false); } private String buildCmfPath(Constrained constrained, int objectId, String propertyName) { return buildCmfPath(constrained, objectId, propertyName, true); } private String buildCmfPath(Constrained constrained, int objectId, String propertyName, boolean useRepository) { String repository = null; if (useRepository) { if (mRepository != null) { repository = mRepository; } if (constrained != null) { ConstrainedProperty property = constrained.getConstrainedProperty(propertyName); if (property != null && property.hasRepository()) { repository = property.getRepository(); } } } StringBuilder path = new StringBuilder(""); if (repository != null && repository.length() > 0) { path.append(repository); path.append(":"); } path.append("/"); String classname = mBackendClass.getName(); classname = classname.substring(classname.lastIndexOf(".")+1); path.append(StringUtils.encodeUrl(classname)); path.append("/"); path.append(objectId); path.append("/"); path.append(StringUtils.encodeUrl(propertyName)); return path.toString().toLowerCase(); } /** * Retrieves a content data representation for use in html. * <p>This is mainly used to integrate content data inside a html * document. For instance, html content will be displayed as-is, while * image content will cause an image tag to be generated with the correct * source URL to serve the image. * * @param bean the bean instance that contains the data * @param propertyName the name of the property whose html representation * will be provided * @param element an active element instance * @param serveContentExitName the exit name that leads to a {@link * com.uwyn.rife.cmf.elements.ServeContent ServeContent} element. This will * be used to generate URLs for content that can't be directly displayed * in-line. * @return the html content representation * @exception ContentManagerException if an unexpected error occurred * @since 1.0 */ public String getContentForHtml(T bean, String propertyName, Element element, String serveContentExitName) throws ContentManagerException { return getContentManager().getContentForHtml(buildCmfPath(bean, propertyName), element, serveContentExitName); } /** * Retrieves a content data representation for use in html. * <p>This is mainly used to integrate content data inside a html * document. For instance, html content will be displayed as-is, while * image content will cause an image tag to be generated with the correct * source URL to serve the image. * * @param objectId the ID of the bean that contains the data * @param propertyName the name of the property whose html representation * will be provided * @param element an active element instance * @param serveContentExitName the exit name that leads to a {@link * com.uwyn.rife.cmf.elements.ServeContent ServeContent} element. This will * be used to generate URLs for content that can't be directly displayed * in-line. * @return the html content representation * @exception ContentManagerException if an unexpected error occurred * @since 1.0 */ public String getContentForHtml(int objectId, String propertyName, Element element, String serveContentExitName) throws ContentManagerException { return getContentManager().getContentForHtml(buildCmfPath(objectId, propertyName), element, serveContentExitName); } /** * Simply clones the instance with the default clone method. This creates * a shallow copy of all fields and the clone will in fact just be another * reference to the same underlying data. The independence of each cloned * instance is consciously not respected since they rely on resources that * can't be cloned. * * @since 1.0 */ public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { ///CLOVER:OFF // this should never happen Logger.getLogger("com.uwyn.rife.cmf").severe(ExceptionUtils.getExceptionStackTrace(e)); return null; ///CLOVER:ON } } class Listener implements GenericQueryManagerListener<T> { public void installed() {} public void removed() {} public void inserted(T bean) { saved(bean); } public void updated(T bean) { saved(bean); } public void saved(T bean) { Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean); Collection<ConstrainedProperty> properties = null; if (constrained != null) { properties = constrained.getConstrainedProperties(); } // process the properties that have to be handled after the saving of the bean if (properties != null) { int id = getIdentifierValue(bean); for (ConstrainedProperty property : properties) { if (property.hasMimeType()) { try { Object value = BeanUtils.getPropertyValue(bean, property.getPropertyName()); if (value != null || property.isAutoRetrieved()) { Content content = new Content(property.getMimeType(), value) .fragment(property.isFragment()) .name(property.getName()) .attributes(property.getContentAttributes()) .cachedLoadedData(property.getCachedLoadedData()); mContentManager.storeContent(buildCmfPath(constrained, id, property.getPropertyName()), content, property.getTransformer()); } } catch (BeanUtilsException e) { throw new DatabaseException(e); } catch (ContentManagerException e) { throw new DatabaseException(e); } } } } } public void restored(T bean) { restoreContent(getIdentifierValue(bean), bean); } public void deleted(int objectId) { T bean = mDeletedbean.get(); if (null == bean) { return; } Constrained constrained = ConstrainedUtils.makeConstrainedInstance(bean); if (constrained != null) { Collection<ConstrainedProperty> properties = constrained.getConstrainedProperties(); for (ConstrainedProperty property : properties) { if (property.hasMimeType()) { mContentManager.deleteContent(buildCmfPath(constrained, objectId, property.getPropertyName())); } else if (property.isOrdinal()) { OrdinalManager ordinals = null; if (property.hasOrdinalRestriction()) { String restriction_name = property.getOrdinalRestriction(); // initialize the ordinal manager, taking the restriction property into account ordinals = new OrdinalManager(getDatasource(), getTable(), property.getPropertyName(), restriction_name); // obtain the restriction value long restriction = -1; try { Object restriction_object = BeanUtils.getPropertyValue(bean, restriction_name); if (null == restriction_object) { throw new OrdinalRestrictionCantBeNullException(bean.getClass(), property.getPropertyName(), restriction_name); } if (!(restriction_object instanceof Number)) { throw new InvalidOrdinalRestrictionTypeException(bean.getClass(), property.getPropertyName(), restriction_name, restriction_object.getClass()); } restriction = ((Number)restriction_object).longValue(); } catch (BeanUtilsException e) { throw new UnknownOrdinalRestrictionException(bean.getClass(), property.getPropertyName(), restriction_name); } // tighten the remaining ordinals, taking the restriction value into account ordinals.tighten(restriction); } else { ordinals = new OrdinalManager(getDatasource(), getTable(), property.getPropertyName()); ordinals.tighten(); } } } } } } public <OtherBeanType> GenericQueryManager<OtherBeanType> createNewManager(Class<OtherBeanType> type) { return new ContentQueryManager(getDatasource(), type, mContentManager); } }