/* * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ package com.arsdigita.cms; import com.arsdigita.domain.DomainObject; import com.arsdigita.domain.DomainService; import com.arsdigita.kernel.ACSObject; import com.arsdigita.persistence.DataObject; import com.arsdigita.persistence.OID; import com.arsdigita.persistence.SessionManager; import com.arsdigita.persistence.metadata.ObjectType; import com.arsdigita.persistence.metadata.Property; import com.arsdigita.util.UncheckedWrapperException; import org.apache.log4j.Logger; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; /** * Provides static methods to instantiate proper subclasses of {@link * com.arsdigita.kernel.ACSObject} domain objects, based on the object * type and the Java class name. * @see com.arsdigita.kernel.ACSObject * * @version $Id: ACSObjectFactory.java 2090 2010-04-17 08:04:14Z pboy $ */ public class ACSObjectFactory extends DomainService { private static Logger s_log = Logger.getLogger(ACSObjectFactory.class); private static Class[] s_args = new Class[]{OID.class}; private static Class[] s_newArgs = new Class[]{String.class}; private static Class[] s_dataArgs = new Class[]{DataObject.class}; /** * Load a proper subclass of the {@link ACSObject} domain object * from the database * * @param javaClass The Java {@link Class} to be instantiated * @param objectType the object type of the object to instantiated; should * extend the ACSObject * @param id the primary key for the new object * @return the loaded object, or null on failure. It is safe to cast * the return value to the javaClass */ public static ACSObject loadACSObject(final Class javaClass, final String objectType, final BigDecimal id) { try { Constructor constr = javaClass.getConstructor(s_args); OID oid = new OID(objectType, id); return (ACSObject)constr.newInstance(new OID[]{oid}); } catch (NoSuchMethodException nsme) { throw new UncheckedWrapperException(nsme); } catch (InstantiationException ie) { throw new UncheckedWrapperException(ie); } catch (IllegalAccessException iae) { throw new UncheckedWrapperException(iae); } catch (InvocationTargetException ite) { throw new UncheckedWrapperException(ite); } } /** * Load a proper subclass of the {@link ACSObject} domain object * from the database * * @param javaClass The name of the Java class to be instantiated * @param objectType the object type of the object to instantiate; should * extend the ACSObject * @param id the primary key for the new object * @return the loaded object, or null on failure. It is safe to cast * the return value to the javaClass */ public static ACSObject loadACSObject(final String javaClass, final String objectType, final BigDecimal id) { try { return loadACSObject(Class.forName(javaClass), objectType, id); } catch (ClassNotFoundException cnfe) { throw new UncheckedWrapperException(cnfe); } } /** * "Cast" the {@link ContentItem} to its proper subclass, based on the * values in its content type. Return the item itself on failure. *

* Note that this method performs 2 extra database hits: one hit * to get the content type, and another hit to get the new item. * * @param item the {@link ContentItem} to cast * @return the proper subclass of {@link ContentItem}, such as * {@link ContentPage} */ public static ContentItem castContentItem(ContentItem item) { String javaClass = null, objectType = null; ContentType t = item.getContentType(); if (t != null) { // Get the java class/objetc type from the content type javaClass = t.getClassName(); objectType = t.getAssociatedObjectType(); } else { // Get the most specific object type and use it as the java class objectType = item.getSpecificObjectType(); javaClass = objectType; } return (ContentItem)loadACSObject( javaClass, objectType, item.getID() ); } /** * Attempt to retrieve the true object type of the item * from the "object_type" column of ACSObject. * On failure, return the type which is recorder in the * item's OID * * @param obj the ACSObject * @return the true (most specific) object type of the object */ public static ObjectType getSpecificObjectType(ACSObject obj) { String typeName = obj.getSpecificObjectType(); return SessionManager.getMetadataRoot().getObjectType(typeName); } /** * Attempt to "cast" a {@link DataObject} to a proper * {@link ACSObject} subclass. Return null if the data object * does not represent an ACSObject. *

* This method assumes that the Java class name is the same * as the object type name of the object. If this assumption * fails, the method will return null. *

* If the assumption turns out to be true, this method * will try to instantiate the desired object with the * constructor public Foo(DataObject obj). * If the constructor does not exist, the method will * return null. * *

The data object obj is specialized to the right * subclass if that is necessary. * * @param obj the {@link DataObject} to cast * @return the proper subclass of {@link ACSObject}, * or null on failure * * @see #castContentItem */ public static ACSObject castACSObject(DataObject obj) { String objType = obj.getOID().getObjectType().getQualifiedName(); // Try to extract the object type column String javaClassName = (String) obj.get(ACSObject.OBJECT_TYPE); if (!javaClassName.equals(objType)) { obj.specialize(javaClassName); } // Try to use the constructor "public Whatever(DataObject obj)" try { final Class javaClass = Class.forName(javaClassName); final Constructor constr = javaClass.getConstructor(s_dataArgs); return (ACSObject) constr.newInstance(new DataObject[] {obj}); } catch (ClassNotFoundException cnfe) { throw new UncheckedWrapperException(cnfe); } catch (NoSuchMethodException nsme) { throw new UncheckedWrapperException(nsme); } catch (InstantiationException ie) { throw new UncheckedWrapperException(ie); } catch (IllegalAccessException iae) { throw new UncheckedWrapperException(iae); } catch (InvocationTargetException ite) { throw new UncheckedWrapperException(ite); } } /** * Attempt to "cast" an {@link ACSObject} to a proper * {@link ACSObject} subclass. Return the original * object on failure *

* This method assumes that the Java class name is the same * as the object type name of the object. If this assumption * fails, the method will return null. *

* * @param obj the {@link ACSObject} to cast * @return the proper subclass of {@link ACSObject}, * or null on failure * * @see #castContentItem */ public static ACSObject castACSObject(ACSObject obj) { String objectTypeName = null; ObjectType specificType = null; ObjectType currentType = obj.getOID().getObjectType(); // Extract the most specific object type objectTypeName = obj.getSpecificObjectType(); if (objectTypeName == null) { // Just get the type from the OID objectTypeName = currentType.getQualifiedName(); } else { specificType = SessionManager.getMetadataRoot().getObjectType(objectTypeName); // Abort if the object type is unknown if (specificType == null) return obj; objectTypeName = specificType.getQualifiedName(); } // If the object is already specific, abort if (obj.getClass().getName().equals(objectTypeName)) return obj; // Try to load the more specific object ACSObject newObj = loadACSObject(objectTypeName, objectTypeName, obj.getID()); if (newObj == null) return obj; return newObj; } /** * Attempt to "cast" a {@link DataObject} to a proper * {@link ContentItem} subclass. Return null if the data object * does not represent a content item. *

* Note that this method performs 2 extra database hits: one hit * to get the content type, and another hit to get the new item. * * @param obj the {@link DataObject} to cast * @param useContentType if true, try to access the item's content type * in order to get the Java class name. If false, assume that * the java class name is the same as the object type. * * @return the proper subclass of {@link ContentItem}, such as * {@link ContentPage} */ public static ContentItem castContentItem(final DataObject obj, final boolean useContentType) { OID oid = obj.getOID(); ContentType t = null; String javaClassName = null; String objectType = (String) obj.get(ACSObject.OBJECT_TYPE); if (objectType == null) { // Get the object type from OID objectType = oid.getObjectType().getQualifiedName(); } // Try to figure out the class name by looking at the // content type if (useContentType) { DataObject tobj = (DataObject) obj.get(ContentItem.CONTENT_TYPE); if (tobj != null) { // Get the content type directly from the object t = new ContentType(tobj); } else { // Ok, try to find the content type through object type t = ContentType.findByAssociatedObjectType(objectType); } if (t != null) { javaClassName = t.getClassName(); } } // On failure, assume that the java class name is the same // as the object type if (javaClassName == null) { javaClassName = objectType; } try { // Try to use the constructor "public Whatever(DataObject obj)" final Class javaClass = Class.forName(javaClassName); final Constructor constr = javaClass.getConstructor(s_dataArgs); return (ContentItem) constr.newInstance(new DataObject[] {obj}); } catch (ClassNotFoundException cnfe) { throw new UncheckedWrapperException(cnfe); } catch (NoSuchMethodException nsme) { try { return (ContentItem) loadACSObject (javaClassName, objectType, (BigDecimal) oid.get(ACSObject.ID)); } catch (ClassCastException ex) { s_log.warn("Assumption (type == java class) for " + objectType + " is incorrect, giving up"); return null; } } catch (InstantiationException ie) { throw new UncheckedWrapperException(ie); } catch (IllegalAccessException iae) { throw new UncheckedWrapperException(iae); } catch (InvocationTargetException ite) { throw new UncheckedWrapperException(ite); } } /** * Attempt to "cast" a {@link DataObject} to a proper * {@link ContentItem} subclass. Return null if the data object * does not represent a content item. *

* Note that this method performs 2 extra database hits: one hit * to get the content type, and another hit to get the new item. * * @param obj the {@link DataObject} to cast * * @return the proper subclass of {@link ContentItem}, such as * {@link ContentPage} */ public static ContentItem castContentItem(DataObject obj) { return castContentItem(obj, true); } /** * Attempt to "cast" a {@link DomainObject} to a proper * {@link ContentItem} subclass. Return null if the domain object * does not represent a content item. *

* Note that this method performs 2 extra database hits: one hit * to get the content type, and another hit to get the new item. * * @param obj the {@link DomainObject} to cast * * @return the proper subclass of {@link ContentItem}, such as * {@link ContentPage} */ public static ContentItem castContentItem(DomainObject obj) { return castContentItem(getDataObject(obj)); } /** * Create a proper subclass of {@link ACSObject}. Requires the class * designated by javaClassName to have a constructor * with a single String parameter. The String parameter should specify * the object type of the new object. * * @param javaClassName The name of the Java {@link Class} * to be instantiated * @param objectType the object type of the object to instantiated; should * extend the {@link ACSObject} * @return the new {@link ACSObject} on success, null on failure */ public static ACSObject createACSObject(final String javaClassName, final String objectType) { try { final Class javaClass = Class.forName(javaClassName); final Constructor constr = javaClass.getConstructor(s_newArgs); ACSObject obj = (ACSObject) constr.newInstance(new String[] {objectType}); return obj; } catch (ClassNotFoundException cnfe) { throw new UncheckedWrapperException(cnfe); } catch (NoSuchMethodException nsme) { throw new UncheckedWrapperException(nsme); } catch (InstantiationException ie) { throw new UncheckedWrapperException(ie); } catch (IllegalAccessException iae) { throw new UncheckedWrapperException(iae); } catch (InvocationTargetException ite) { throw new UncheckedWrapperException(ite); } } /** * Instantiate a subclass of {@link ACSObject}, * blatantly disregarding ObjectType. * * @param javaClassName The name of the Java {@link Class} * to be instantiated * * @return the new {@link ACSObject} on success, null on failure */ public static ACSObject createACSObject(String javaClassName) { try { final Class javaClass = Class.forName(javaClassName); final ACSObject obj = (ACSObject) javaClass.newInstance(); return obj; } catch (ClassNotFoundException cnfe) { throw new UncheckedWrapperException(cnfe); } catch (InstantiationException ie) { throw new UncheckedWrapperException(ie); } catch (IllegalAccessException iae) { throw new UncheckedWrapperException(iae); } } /** * Get a collection of all attributes which comprise the type's * primary key * * @return an array of primary key attribute names */ public static Collection getKeyAttributes(final ObjectType type) { final ArrayList keys = new ArrayList(1); for (Iterator it = type.getKeyProperties(); it.hasNext();) { keys.add(it.next()); } return keys; } /** * Get an array of all attribute names which comprise the type's * primary key * * @return an array of primary key attribute names */ public static Collection getKeyAttributeNames(final ObjectType type) { final Collection attrs = getKeyAttributes(type); final ArrayList names = new ArrayList(attrs.size()); for (Iterator i = attrs.iterator(); i.hasNext();) { names.add(((Property)i.next()).getName()); } return names; } }