From: <pb...@fe...> - 2013-02-03 08:28:39
|
Author: pboy Date: 2013-02-03 08:28:27 +0000 (Sun, 03 Feb 2013) New Revision: 2480 Modified: trunk/ccm-cms/src/com/arsdigita/cms/Folder.java Log: API addition: Load a folder by Path. (on behalf of JensP) Modified: trunk/ccm-cms/src/com/arsdigita/cms/Folder.java =================================================================== --- trunk/ccm-cms/src/com/arsdigita/cms/Folder.java 2013-02-02 23:46:44 UTC (rev 2479) +++ trunk/ccm-cms/src/com/arsdigita/cms/Folder.java 2013-02-03 08:28:27 UTC (rev 2480) @@ -48,21 +48,15 @@ import java.util.Iterator; /** - * This class represents folders for which to organize items in a tree - * hierarchy. + * This class represents folders for which to organize items in a tree hierarchy. * - * Folders will only ever exist as draft or live versions. There - * should never be any folders that are pending. The pending versions - * of ordinary content items are stored in the live version of - * folders. + * Folders will only ever exist as draft or live versions. There should never be any folders that are pending. The + * pending versions of ordinary content items are stored in the live version of folders. * - * Folders cannot have their own lifecycles. The methods to get or set - * lifecycles are no-ops. + * Folders cannot have their own lifecycles. The methods to get or set lifecycles are no-ops. * - * You should never call {@link #publish} or {@link #unpublish} on a - * folder; at present, these methods only log a warning when they are - * called. In the future, these warnings may be turned into actual - * errors. + * You should never call {@link #publish} or {@link #unpublish} on a folder; at present, these methods only log a + * warning when they are called. In the future, these warnings may be turned into actual errors. * * @author Jack Chung * @author Michael Pih @@ -72,24 +66,20 @@ public class Folder extends ContentItem { private static final Logger s_log = Logger.getLogger(Folder.class); - public static final String BASE_DATA_OBJECT_TYPE = - "com.arsdigita.cms.Folder"; - + "com.arsdigita.cms.Folder"; public static final String INDEX = "index"; public static final String HOME_FOLDER = "homeFolder"; public static final String HOME_SECTION = "homeSection"; - private static final String ITEMS_QUERY = "com.arsdigita.cms.ItemsInFolder"; private static final String PRIMARY_INSTANCES_QUERY = - "com.arsdigita.cms.PrimaryInstancesInFolder"; + "com.arsdigita.cms.PrimaryInstancesInFolder"; private static final String ITEM_QUERY = "com.arsdigita.cms.ItemInFolder"; private static final String FOLDER_QUERY = "com.arsdigita.cms.FolderInFolder"; private static final String LABEL = "label"; private static final String NAME = "name"; private final static String ITEM = "item"; private boolean m_wasNew; - protected static final String ITEMS = "items"; /** @@ -100,25 +90,24 @@ } /** - * Constructor. The contained <code>DataObject</code> is retrieved - * from the persistent storage mechanism with an <code>OID</code> - * specified by <cod>oid</code>. + * Constructor. The contained + * <code>DataObject</code> is retrieved from the persistent storage mechanism with an + * <code>OID</code> specified by <cod>oid</code>. * - * @param oid The <code>OID</code> for the retrieved - * <code>DataObject</code>. + * @param oid The <code>OID</code> for the retrieved <code>DataObject</code>. */ public Folder(final OID oid) throws DataObjectNotFoundException { super(oid); } /** - * Constructor. The contained <code>DataObject</code> is retrieved - * from the persistent storage mechanism with an <code>OID</code> - * specified by <code>id</code> and + * Constructor. The contained + * <code>DataObject</code> is retrieved from the persistent storage mechanism with an + * <code>OID</code> specified by + * <code>id</code> and * <code>Folder.BASE_DATA_OBJECT_TYPE</code>. * - * @param id The <code>id</code> for the retrieved - * <code>DataObject</code> + * @param id The <code>id</code> for the retrieved <code>DataObject</code> */ public Folder(final BigDecimal id) throws DataObjectNotFoundException { this(new OID(BASE_DATA_OBJECT_TYPE, id)); @@ -145,9 +134,10 @@ item.copy(this, true); } } + /** - * @return the base PDL object type for this item. Child classes - * should override this method to return the correct value + * @return the base PDL object type for this item. Child classes should override this method to return the correct + * value */ public String getBaseDataObjectType() { return BASE_DATA_OBJECT_TYPE; @@ -162,18 +152,16 @@ s_log.debug("Deleting folder"); if (!isEmpty()) { - throw new IllegalStateException - ("Attempt to delete non-empty folder " + getOID() + "; " + - "only empty folders can be deleted"); + throw new IllegalStateException("Attempt to delete non-empty folder " + getOID() + "; " + + "only empty folders can be deleted"); } super.delete(); } protected void beforeDelete() { - DataCollection maps = SessionManager.getSession().retrieve - (UserHomeFolderMap.BASE_DATA_OBJECT_TYPE); - maps.addEqualsFilter(HOME_FOLDER + "." + ID,getID()); + DataCollection maps = SessionManager.getSession().retrieve(UserHomeFolderMap.BASE_DATA_OBJECT_TYPE); + maps.addEqualsFilter(HOME_FOLDER + "." + ID, getID()); while (maps.next()) { maps.getDataObject().delete(); } @@ -207,9 +195,8 @@ final ContentSection section = getContentSection(); - if (section != null && - (this.equals(section.getRootFolder()) || - this.equals(section.getTemplatesFolder()))) { + if (section != null && (this.equals(section.getRootFolder()) || this. + equals(section.getTemplatesFolder()))) { PermissionService.setContext(this, section); } } @@ -225,9 +212,8 @@ } /** - * Fetches the child items of this folder. The returned collection - * provides methods to filter by various criteria, for example by - * name or by whether items are folders or not. + * Fetches the child items of this folder. The returned collection provides methods to filter by various criteria, + * for example by name or by whether items are folders or not. * * @param bSort whether to sort the collection by isFolder and ID * @return child items of this folder @@ -243,10 +229,9 @@ } /** - * Fetches the child items of this folder. The returned collection - * provides methods to filter by various criteria, for example by - * name or by whether items are folders or not. The items returned - * by this method are sorted by isFolder and ID + * Fetches the child items of this folder. The returned collection provides methods to filter by various criteria, + * for example by name or by whether items are folders or not. The items returned by this method are sorted by + * isFolder and ID * * @return child items of this folder, sorted by isFolder and ID */ @@ -254,14 +239,11 @@ return getItems(true); } - /** - * Returns collection of primary language instances for bundles in - * this folder. + * Returns collection of primary language instances for bundles in this folder. */ public ItemCollection getPrimaryInstances() { - final DataQuery query = SessionManager.getSession().retrieveQuery - (PRIMARY_INSTANCES_QUERY); + final DataQuery query = SessionManager.getSession().retrieveQuery(PRIMARY_INSTANCES_QUERY); query.setParameter(PARENT, getID()); Assert.isNotEqual(PENDING, getVersion()); @@ -272,14 +254,11 @@ } /** - * Returns a child content item in this folder (which could itself - * be a folder) with the specified name. + * Returns a child content item in this folder (which could itself be a folder) with the specified name. * * @param name The name of the item - * @param isFolder If true, only return a subfolder. Otherwise, - * return any subitem - * @return The item with the given name, or null if no such item - * exists in the folder + * @param isFolder If true, only return a subfolder. Otherwise, return any subitem + * @return The item with the given name, or null if no such item exists in the folder */ public ContentItem getItem(final String name, final boolean isFolder) { @@ -299,14 +278,13 @@ if (items.next()) { DataObject dataObj = items.getDataObject(); - ContentItem result = (ContentItem)DomainObjectFactory - .newInstance(dataObj); + ContentItem result = (ContentItem) DomainObjectFactory + .newInstance(dataObj); if (items.next()) { - s_log.warn("Item in folder has a duplicate name; one " + - "is " + result + " and one is " + - (ContentItem)DomainObjectFactory - .newInstance(items.getDataObject())); + s_log.warn("Item in folder has a duplicate name; one " + "is " + result + " and one is " + + (ContentItem) DomainObjectFactory + .newInstance(items.getDataObject())); throw new IllegalStateException(); } @@ -334,7 +312,6 @@ } } - /** * Fetches the label of the folder. */ @@ -354,12 +331,10 @@ } /** - * Set the version of the folder. An attempt to set the version to - * pending will result in the folder's version being set to live. We will - * never have any pending versions of folders, only live or draft. + * Set the version of the folder. An attempt to set the version to pending will result in the folder's version being + * set to live. We will never have any pending versions of folders, only live or draft. * - * Pending versions of items are stored in the live version of a - * folder. + * Pending versions of items are stored in the live version of a folder. */ protected void setVersion(String version) { if (ContentItem.PENDING.equals(version)) { @@ -372,7 +347,6 @@ // // Publish/unpublish stuff // - public void unpublish() { if (s_log.isInfoEnabled()) { s_log.info("Unpublishing folder " + this); @@ -393,9 +367,9 @@ // // Lifecycle stuff // - /** - * Always returns <code>null</code>, as folders do not have lifecycles. + * Always returns + * <code>null</code>, as folders do not have lifecycles. * * @return a <code>Lifecycle</code> value */ @@ -435,16 +409,12 @@ // // Index item // - /** - * Get the (special) index item for the folder. The index item is - * what carries all the user-editable attributes of the - * folder. The index item is what should be published when a index - * page for a folder is desired. + * Get the (special) index item for the folder. The index item is what carries all the user-editable attributes of + * the folder. The index item is what should be published when a index page for a folder is desired. * - * The index item is an ordinary item in every respect, i.e., it - * is part of the collection returned by <code>getItems()</code>, - * you cannot delete a folder if it still has an index item etc. + * The index item is an ordinary item in every respect, i.e., it is part of the collection returned by + * <code>getItems()</code>, you cannot delete a folder if it still has an index item etc. */ public ContentBundle getIndexItem() { // BECAUSE INDEX ITEM MIGHT NOT BE UPDATED FOR PUBLISHED @@ -453,7 +423,7 @@ if (getVersion().compareTo(ContentItem.LIVE) == 0) { final ContentItem indexItem = - ((Folder) getWorkingVersion()).getIndexItem(); + ((Folder) getWorkingVersion()).getIndexItem(); if (indexItem == null) { return null; @@ -478,8 +448,7 @@ /** * Sets the index item. This also adds the item to the folder. * - * @param item The index item with the folder's user-editable - * attributes + * @param item The index item with the folder's user-editable attributes */ public final void setIndexItem(final ContentBundle item) { setAssociation(INDEX, item); @@ -495,15 +464,15 @@ } /** - * Returns <code>true</code> if the folder is empty. + * Returns + * <code>true</code> if the folder is empty. * * @return <code>true</code> if the folder is empty */ public boolean isEmpty() { final Session session = SessionManager.getSession(); - final DataQuery query = session.retrieveQuery - ("com.arsdigita.cms.folderNotEmpty"); + final DataQuery query = session.retrieveQuery("com.arsdigita.cms.folderNotEmpty"); query.setParameter("id", getID()); final boolean result = !query.next(); @@ -514,17 +483,17 @@ } /** - * Returns <code>true</code> if the folder contains at least one - * folder, <code>false</code> if the folder does not contain any - * folders, but is either empty or contains only ordinary items. + * Returns + * <code>true</code> if the folder contains at least one folder, + * <code>false</code> if the folder does not contain any folders, but is either empty or contains only ordinary + * items. * * @return <code>true</code> if the folder contains other folders. */ public boolean containsFolders() { final Session session = SessionManager.getSession(); - final DataQuery query = session.retrieveQuery - ("com.arsdigita.cms.folderHasNoSubFolders"); + final DataQuery query = session.retrieveQuery("com.arsdigita.cms.folderHasNoSubFolders"); query.setParameter("id", getID()); final boolean result = !query.next(); @@ -535,25 +504,23 @@ } /** - * Copy the specified property (attribute or association) from the specified - * source folder. This method almost completely overrides the - * metadata-driven methods in <code>ObjectCopier</code>. If the property in - * question is an association to <code>ContentItem</code>(s), this method - * should <em>only</em> call <code>FooContentItem newChild = - * copier.copyItem(originalChild)</code>. An attempt to call any other - * method in order to copy the child will most likely have disastrous - * consequences. + * Copy the specified property (attribute or association) from the specified source folder. This method almost + * completely overrides the metadata-driven methods in + * <code>ObjectCopier</code>. If the property in question is an association to + * <code>ContentItem</code>(s), this method should <em>only</em> call + * <code>FooContentItem newChild = + * copier.copyItem(originalChild)</code>. An attempt to call any other method in order to copy the child will most + * likely have disastrous consequences. * * If a child class overrides this method, it should return - * <code>super.copyProperty</code> in order to indicate that it is - * not interested in handling the property in any special way. + * <code>super.copyProperty</code> in order to indicate that it is not interested in handling the property in any + * special way. * * @param srcItem the source item * @param property the property to copy * @param copier the ItemCopier - * @return true if the property was copied, false to indicate that - * regular metadata-driven methods should be used to copy the - * property + * @return true if the property was copied, false to indicate that regular metadata-driven methods should be used to + * copy the property */ public boolean copyProperty(final CustomCopy srcItem, final Property property, @@ -575,42 +542,41 @@ } /** - * A collection of items that can be filtered to return only folders or - * only nonfolders. + * A collection of items that can be filtered to return only folders or only nonfolders. */ public static class ItemCollection - extends com.arsdigita.cms.ItemCollection { + extends com.arsdigita.cms.ItemCollection { + private final static String IS_FOLDER = "isFolder"; private final static String HAS_CHILDREN = "hasChildren"; private final static String ITEM = "item"; private final static String HAS_LIVE_VERSION = "hasLiveVersion"; private final static String TYPE_LABEL = "type.label"; - private final static String AUDIT_TRAIL="item.auditing"; - + private final static String AUDIT_TRAIL = "item.auditing"; private DataQuery m_query; - /** * Constructor - * @param adapter an adapter constructed using the query name rather than a - * DataQuery object. This constructor must be used if there is any - * intention to permission filter the results as only a DataQueryDataCollectionAdapter - * constructed using query name has the bug fix to allow permission filtering * + * @param adapter an adapter constructed using the query name rather than a DataQuery object. This constructor + * must be used if there is any intention to permission filter the results as only a + * DataQueryDataCollectionAdapter constructed using query name has the bug fix to allow permission filtering + * * @param bSort whether to sort the collection by isFolder and ID */ - public ItemCollection (DataQueryDataCollectionAdapter adapter, boolean bSort) { + public ItemCollection(DataQueryDataCollectionAdapter adapter, boolean bSort) { super(adapter); doAlias(adapter); init(adapter, bSort); } - public ItemCollection (DataQueryDataCollectionAdapter adapter) { + public ItemCollection(DataQueryDataCollectionAdapter adapter) { this(adapter, true); } /** * Constructor + * * @param query the Data Query to use to retrieve the collection * @param bSort whether to sort the collection by isFolder and ID */ @@ -623,14 +589,12 @@ } /** - * Convenience Constructor that always sorts the collection - * by isFolder and ID - * - * jensp 2011-06: I changed this because this silly sorting affects - * the ItemSearchWidget and makes it pretty useless... I've not noticed - * any negative effects, so it seams no problem. Sorting is now set by - * the caller/user of the {@code ItemCollection}. - * + * Convenience Constructor that always sorts the collection by isFolder and ID + * + * jensp 2011-06: I changed this because this silly sorting affects the ItemSearchWidget and makes it pretty + * useless... I've not noticed any negative effects, so it seams no problem. Sorting is now set by the + * caller/user of the {@code ItemCollection}. + * * @param query the Data Query to use to retrieve the collection */ public ItemCollection(DataQuery query) { @@ -656,8 +620,7 @@ } /** - * Sets the range of the dataquery. This is used by the - * paginator. + * Sets the range of the dataquery. This is used by the paginator. * * @param beginIndex The start index * @param endIndex The end index @@ -672,14 +635,12 @@ } /** - * For performance reaons, override superclass methods and - * try to get the audit info without instantiating a content item. - * We know this can help because the getPrimaryInstances - * query retrieves the audit info directly + * For performance reaons, override superclass methods and try to get the audit info without instantiating a + * content item. We know this can help because the getPrimaryInstances query retrieves the audit info directly */ public Date getCreationDate() { DataObject dobj = (DataObject) get(AUDIT_TRAIL); - if (dobj != null){ + if (dobj != null) { BasicAuditTrail audit = new BasicAuditTrail(dobj); return audit.getCreationDate(); } else { @@ -689,7 +650,7 @@ public Date getLastModifiedDate() { DataObject dobj = (DataObject) get(AUDIT_TRAIL); - if (dobj != null){ + if (dobj != null) { BasicAuditTrail audit = new BasicAuditTrail(dobj); return audit.getLastModifiedDate(); } else { @@ -698,9 +659,8 @@ } /** - * Return the pretty name of the content type of the current item. If - * the current item is a folder, the string <tt>Folder</tt> is - * returned, otherwise the label of the item's content type. + * Return the pretty name of the content type of the current item. If the current item is a folder, the string + * <tt>Folder</tt> is returned, otherwise the label of the item's content type. * * @return the pretty name of the content type of the current item. */ @@ -708,16 +668,15 @@ if (isFolder()) { return "Folder"; } else { - return (String) get(TYPE_LABEL); + return (String) get(TYPE_LABEL); } } /** * Filter the collection by whether items are folders or not. * - * @param v <code>true</code> if the data query should only list folders, - * <code>false</code> if the data query should only list non-folder - * items. + * @param v <code>true</code> if the data query should only list folders, <code>false</code> if the data query + * should only list non-folder items. * */ public void addFolderFilter(final boolean v) { @@ -725,11 +684,10 @@ } /** - * Return <code>true</code> if the current item in the collection is a - * folder. + * Return + * <code>true</code> if the current item in the collection is a folder. * - * @return <code>true</code> if the current item in the collection is a - * folder. + * @return <code>true</code> if the current item in the collection is a folder. */ public boolean isFolder() { Boolean result = (Boolean) m_query.get(IS_FOLDER); @@ -743,7 +701,7 @@ public boolean isLive() { String version = (String) get(ContentItem.VERSION); - if (ContentItem.LIVE.equals(version) ) { + if (ContentItem.LIVE.equals(version)) { return true; } Boolean hasLive = (Boolean) m_query.get(HAS_LIVE_VERSION); @@ -763,12 +721,11 @@ } /** - * Called by <code>VersionCopier</code> to determine whether to - * publish associated items when an item goes live. This will only - * have an effect for non-component associations where the item is - * not yet published. Override default for <code>Folder</code>s - * since they don't have their own lifecycles and a folder must be - * published when an item in it goes live. + * Called by + * <code>VersionCopier</code> to determine whether to publish associated items when an item goes live. This will + * only have an effect for non-component associations where the item is not yet published. Override default for + * <code>Folder</code>s since they don't have their own lifecycles and a folder must be published when an item in it + * goes live. * * @return whether to publish this item */ @@ -776,18 +733,18 @@ return true; } - public static void setUserHomeFolder(User user,Folder folder) { - UserHomeFolderMap map = UserHomeFolderMap.findOrCreateUserHomeFolderMap(user,folder.getContentSection()); + public static void setUserHomeFolder(User user, Folder folder) { + UserHomeFolderMap map = UserHomeFolderMap.findOrCreateUserHomeFolderMap(user, folder.getContentSection()); map.setHomeFolder(folder); map.save(); } - public static Folder getUserHomeFolder(User user,ContentSection section) { + public static Folder getUserHomeFolder(User user, ContentSection section) { Folder folder = null; - UserHomeFolderMap map = UserHomeFolderMap.findUserHomeFolderMap(user,section); - if ( map != null ) { + UserHomeFolderMap map = UserHomeFolderMap.findUserHomeFolderMap(user, section); + if (map != null) { folder = map.getHomeFolder(); - if ( folder != null ) { + if (folder != null) { CMSContext context = CMS.getContext(); SecurityManager sm; if (context.hasSecurityManager()) { @@ -795,11 +752,106 @@ } else { sm = new SecurityManager(section); } - if ( !sm.canAccess(user,SecurityConstants.PREVIEW_PAGES,folder) ) { + if (!sm.canAccess(user, SecurityConstants.PREVIEW_PAGES, folder)) { folder = null; } } } return folder; } + + /** + * Retrieves a folder by its path from a given content section. + * + * @param section The content section from which the folder should be retrieved. + * @param path The path of the folder, relative to the content section. + * @return The folder with the given path from the provided content section. If there is no such folder, + * {@code null} is returned. It is up to the caller to check the returned value for {@code null} and take + * appropriate actions. + */ + public static Folder retrieveFolder(final ContentSection section, final String path) { + if (section == null) { + throw new IllegalArgumentException("No content section provided."); + } + + if ((path == null) || path.isEmpty()) { + throw new IllegalArgumentException("No path provided."); + } + + if (path.charAt(0) != '/') { + throw new IllegalArgumentException("Provided path is not an absolute path (starting with '/')."); + } + + final String[] pathTokens = path.split("/"); + + final Folder rootFolder = section.getRootFolder(); + + Folder folder = rootFolder; + for (String token : pathTokens) { + if ((token == null) || token.isEmpty() || "/".equals(token)) { + continue; + } + + folder = getSubFolder(token, folder); + + if (folder == null) { + break; + } + } + + return folder; + } + + private static Folder getSubFolder(final String name, final Folder fromFolder) { + final ItemCollection items = fromFolder.getItems(); + items.addFolderFilter(true); + items.addNameFilter(name); + + if (items.next()) { + return (Folder) items.getDomainObject(); + } else { + return null; + } + } + + /** + * Retrieves a folder of the current content section by its path. The path is given in a UNIX like synatax and must + * be an absolute path starting with '/'. The path may be precceded with the name of content section, separated by + * ':'. If a content section if given, the path is relative to this content section. If the no content section is + * given, the current content section returned by {@code CMS.getContext().getContentSection()}. Please note that + * {@code CMS.getContext().getContentSection()} may return null. + * + * Examples for valid paths: + * + * <pre> + * /persons/members + * content:/persons/members + * publications:/monographs + * </pre> + * + * @param path The path of the folder to retrieve relative to the current content section. + * @return The folder with the given path from the content section. If there is no such folder, {@code null} is + * returned. It is up to the caller to check the returned value for {@code null} and take appropriate actions. + */ + public static Folder retrieveFolder(final String path) { + final String[] tokens = path.split(":"); + + if (tokens.length == 1) { + return retrieveFolder(CMS.getContext().getContentSection(), path); + } else if (tokens.length == 2) { + final ContentSectionCollection sections = ContentSection.getAllSections(); + sections.addEqualsFilter("label", tokens[0]); + + if (sections.isEmpty()) { + return null; + } else { + sections.next(); + final ContentSection section = sections.getContentSection(); + return retrieveFolder(section, tokens[1]); + } + } else { + throw new IllegalArgumentException("Invalid path syntax. Valid syntax: " + + "[contentsection:]/path/to/folder'"); + } + } } |