ContentQueryManager.java :  » Web-Framework » rife-1.6.1 » com » uwyn » rife » cmf » dam » Java Open Source

Java Open Source » Web Framework » rife 1.6.1 
rife 1.6.1 » com » uwyn » rife » cmf » dam » ContentQueryManager.java
/*
 * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
 * Distributed under the terms of either:
 * - the common development and distribution license (CDDL), v1.0; or
 * - the GNU Lesser General Public License, v2.1 or later
 * $Id: ContentQueryManager.java 3848 2007-07-12 08:55:48Z 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.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: 3848 $
 * @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);
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.