/* * This library is part of OpenCms - * the Open Source Content Management System * * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com) * * 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. * * For further information about Alkacon Software, please see the * company website: http://www.alkacon.com * * For further information about OpenCms, please see the * project website: http://www.opencms.org * * 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 org.opencms.ade.sitemap; import org.opencms.ade.configuration.CmsADEConfigData; import org.opencms.ade.configuration.CmsADEManager; import org.opencms.ade.configuration.CmsFunctionReference; import org.opencms.ade.configuration.CmsModelPageConfig; import org.opencms.ade.configuration.CmsResourceTypeConfig; import org.opencms.ade.detailpage.CmsDetailPageConfigurationWriter; import org.opencms.ade.detailpage.CmsDetailPageInfo; import org.opencms.ade.sitemap.shared.CmsClientSitemapEntry; import org.opencms.ade.sitemap.shared.CmsClientSitemapEntry.EntryType; import org.opencms.ade.sitemap.shared.CmsDetailPageTable; import org.opencms.ade.sitemap.shared.CmsNewResourceInfo; import org.opencms.ade.sitemap.shared.CmsSitemapChange; import org.opencms.ade.sitemap.shared.CmsSitemapChange.ChangeType; import org.opencms.ade.sitemap.shared.CmsSitemapClipboardData; import org.opencms.ade.sitemap.shared.CmsSitemapData; import org.opencms.ade.sitemap.shared.CmsSitemapInfo; import org.opencms.ade.sitemap.shared.rpc.I_CmsSitemapService; import org.opencms.file.CmsFile; import org.opencms.file.CmsObject; import org.opencms.file.CmsProject; import org.opencms.file.CmsProperty; import org.opencms.file.CmsPropertyDefinition; import org.opencms.file.CmsResource; import org.opencms.file.CmsResourceFilter; import org.opencms.file.CmsUser; import org.opencms.file.CmsVfsResourceNotFoundException; import org.opencms.file.history.CmsHistoryResourceHandler; import org.opencms.file.types.CmsResourceTypeFolder; import org.opencms.file.types.CmsResourceTypeFolderExtended; import org.opencms.file.types.CmsResourceTypeXmlContainerPage; import org.opencms.file.types.I_CmsResourceType; import org.opencms.flex.CmsFlexController; import org.opencms.gwt.CmsGwtService; import org.opencms.gwt.CmsRpcException; import org.opencms.gwt.CmsTemplateFinder; import org.opencms.gwt.shared.CmsBrokenLinkBean; import org.opencms.gwt.shared.CmsClientLock; import org.opencms.gwt.shared.CmsCoreData; import org.opencms.gwt.shared.property.CmsClientProperty; import org.opencms.gwt.shared.property.CmsPropertyModification; import org.opencms.i18n.CmsLocaleManager; import org.opencms.json.JSONArray; import org.opencms.jsp.CmsJspNavBuilder; import org.opencms.jsp.CmsJspNavElement; import org.opencms.loader.CmsLoaderException; import org.opencms.lock.CmsLock; import org.opencms.main.CmsException; import org.opencms.main.CmsLog; import org.opencms.main.OpenCms; import org.opencms.search.galleries.CmsGallerySearch; import org.opencms.search.galleries.CmsGallerySearchResult; import org.opencms.security.CmsPermissionSet; import org.opencms.security.CmsSecurityException; import org.opencms.site.CmsSite; import org.opencms.util.CmsDateUtil; import org.opencms.util.CmsStringUtil; import org.opencms.util.CmsUUID; import org.opencms.workplace.CmsWorkplaceMessages; import org.opencms.xml.CmsXmlException; import org.opencms.xml.I_CmsXmlDocument; import org.opencms.xml.containerpage.CmsContainerBean; import org.opencms.xml.containerpage.CmsContainerElementBean; import org.opencms.xml.containerpage.CmsContainerPageBean; import org.opencms.xml.containerpage.CmsXmlContainerPage; import org.opencms.xml.containerpage.CmsXmlContainerPageFactory; import org.opencms.xml.containerpage.CmsXmlDynamicFunctionHandler; import org.opencms.xml.content.CmsXmlContentFactory; import org.opencms.xml.content.CmsXmlContentProperty; import java.text.DateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import com.google.common.collect.ComparisonChain; import com.google.common.collect.Ordering; /** * Handles all RPC services related to the vfs sitemap.<p> * * @since 8.0.0 * * @see org.opencms.ade.sitemap.shared.rpc.I_CmsSitemapService * @see org.opencms.ade.sitemap.shared.rpc.I_CmsSitemapServiceAsync */ public class CmsVfsSitemapService extends CmsGwtService implements I_CmsSitemapService { /** Helper class for representing information about a lock. */ protected class LockInfo { /** The lock state. */ private CmsLock m_lock; /** True if the lock was just locked. */ private boolean m_wasJustLocked; /** * Creates a new LockInfo object.<p> * * @param lock the lock state * @param wasJustLocked true if the lock was just locked */ public LockInfo(CmsLock lock, boolean wasJustLocked) { m_lock = lock; m_wasJustLocked = wasJustLocked; } /** * Returns the lock state.<p> * * @return the lock state */ public CmsLock getLock() { return m_lock; } /** * Returns true if the lock was just locked.<p> * * @return true if the lock was just locked */ public boolean wasJustLocked() { return m_wasJustLocked; } } /** The configuration key for the functionDetail attribute in the container.info property. */ public static final String KEY_FUNCTION_DETAIL = "functionDetail"; /** The additional user info key for deleted list. */ private static final String ADDINFO_ADE_DELETED_LIST = "ADE_DELETED_LIST"; /** The additional user info key for modified list. */ private static final String ADDINFO_ADE_MODIFIED_LIST = "ADE_MODIFIED_LIST"; /** The static log object for this class. */ private static final Log LOG = CmsLog.getLog(CmsVfsSitemapService.class); /** The redirect recource type name. */ private static final String RECOURCE_TYPE_NAME_REDIRECT = "htmlredirect"; /** The redirect target XPath. */ private static final String REDIRECT_LINK_TARGET_XPATH = "Link"; /** Serialization uid. */ private static final long serialVersionUID = -7236544324371767330L; /** The navigation builder. */ private CmsJspNavBuilder m_navBuilder; /** * Returns a new configured service instance.<p> * * @param request the current request * * @return a new service instance */ public static CmsVfsSitemapService newInstance(HttpServletRequest request) { CmsVfsSitemapService service = new CmsVfsSitemapService(); service.setCms(CmsFlexController.getCmsObject(request)); service.setRequest(request); return service; } /** * @see org.opencms.ade.sitemap.shared.rpc.I_CmsSitemapService#createSubSitemap(org.opencms.util.CmsUUID) */ public CmsSitemapChange createSubSitemap(CmsUUID entryId) throws CmsRpcException { CmsObject cms = getCmsObject(); try { ensureSession(); CmsResource subSitemapFolder = cms.readResource(entryId); ensureLock(subSitemapFolder); String sitePath = cms.getSitePath(subSitemapFolder); String folderName = CmsStringUtil.joinPaths(sitePath, CmsADEManager.CONFIG_FOLDER_NAME + "/"); String sitemapConfigName = CmsStringUtil.joinPaths(folderName, CmsADEManager.CONFIG_FILE_NAME); if (!cms.existsResource(folderName)) { cms.createResource( folderName, OpenCms.getResourceManager().getResourceType(CmsADEManager.CONFIG_FOLDER_TYPE).getTypeId()); } I_CmsResourceType configType = OpenCms.getResourceManager().getResourceType(CmsADEManager.CONFIG_TYPE); if (cms.existsResource(sitemapConfigName)) { CmsResource configFile = cms.readResource(sitemapConfigName); if (configFile.getTypeId() != configType.getTypeId()) { throw new CmsException(Messages.get().container( Messages.ERR_CREATING_SUB_SITEMAP_WRONG_CONFIG_FILE_TYPE_2, sitemapConfigName, CmsADEManager.CONFIG_TYPE)); } } else { cms.createResource( sitemapConfigName, OpenCms.getResourceManager().getResourceType(CmsADEManager.CONFIG_TYPE).getTypeId()); } subSitemapFolder.setType(getEntryPointType()); cms.writeResource(subSitemapFolder); tryUnlock(subSitemapFolder); CmsSitemapClipboardData clipboard = getClipboardData(); CmsClientSitemapEntry entry = toClientEntry( getNavBuilder().getNavigationForResource(sitePath, CmsResourceFilter.ONLY_VISIBLE), false); clipboard.addModified(entry); setClipboardData(clipboard); CmsSitemapChange result = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.modify); result.setUpdatedEntry(entry); return result; } catch (Exception e) { error(e); } return null; } /** * @see org.opencms.ade.sitemap.shared.rpc.I_CmsSitemapService#getChildren(java.lang.String, org.opencms.util.CmsUUID, int) */ public CmsClientSitemapEntry getChildren(String entryPointUri, CmsUUID entryId, int levels) throws CmsRpcException { CmsClientSitemapEntry entry = null; try { CmsObject cms = getCmsObject(); //ensure that root ends with a '/' if it's a folder CmsResource rootRes = cms.readResource(entryId, CmsResourceFilter.ONLY_VISIBLE); String root = cms.getSitePath(rootRes); CmsJspNavElement navElement = getNavBuilder().getNavigationForResource(root, CmsResourceFilter.ONLY_VISIBLE); boolean isRoot = root.equals(entryPointUri); entry = toClientEntry(navElement, isRoot); if ((levels > 0) && (isRoot || (rootRes.isFolder() && (!isSubSitemap(navElement))))) { entry.setSubEntries(getChildren(root, levels, null), null); } } catch (Throwable e) { error(e); } return entry; } /** * @see org.opencms.ade.sitemap.shared.rpc.I_CmsSitemapService#mergeSubSitemap(java.lang.String, org.opencms.util.CmsUUID) */ public CmsSitemapChange mergeSubSitemap(String entryPoint, CmsUUID subSitemapId) throws CmsRpcException { CmsObject cms = getCmsObject(); try { ensureSession(); CmsResource subSitemapFolder = cms.readResource(subSitemapId, CmsResourceFilter.ONLY_VISIBLE); ensureLock(subSitemapFolder); subSitemapFolder.setType(OpenCms.getResourceManager().getResourceType( CmsResourceTypeFolder.RESOURCE_TYPE_NAME).getTypeId()); cms.writeResource(subSitemapFolder); String sitePath = cms.getSitePath(subSitemapFolder); String sitemapConfigName = CmsStringUtil.joinPaths( sitePath, CmsADEManager.CONFIG_FOLDER_NAME, CmsADEManager.CONFIG_FILE_NAME); if (cms.existsResource(sitemapConfigName)) { cms.deleteResource(sitemapConfigName, CmsResource.DELETE_PRESERVE_SIBLINGS); } tryUnlock(subSitemapFolder); CmsSitemapClipboardData clipboard = getClipboardData(); CmsClientSitemapEntry entry = toClientEntry( getNavBuilder().getNavigationForResource( cms.getSitePath(subSitemapFolder), CmsResourceFilter.ONLY_VISIBLE), false); clipboard.addModified(entry); setClipboardData(clipboard); entry = getChildren(entryPoint, subSitemapId, 1); CmsSitemapChange result = new CmsSitemapChange(entry.getId(), entry.getSitePath(), ChangeType.modify); result.setUpdatedEntry(entry); return result; } catch (Exception e) { error(e); } return null; } /** * @see org.opencms.ade.sitemap.shared.rpc.I_CmsSitemapService#prefetch(java.lang.String) */ public CmsSitemapData prefetch(String sitemapUri) throws CmsRpcException { CmsSitemapData result = null; CmsObject cms = getCmsObject(); try { String openPath = getRequest().getParameter(CmsCoreData.PARAM_PATH); if (!isValidOpenPath(cms, openPath)) { // if no path is supplied, start from root openPath = "/"; } CmsADEConfigData configData = OpenCms.getADEManager().lookupConfiguration( cms, cms.getRequestContext().addSiteRoot(openPath)); Map<String, CmsXmlContentProperty> propertyConfig = new LinkedHashMap<String, CmsXmlContentProperty>( configData.getPropertyConfigurationAsMap()); Map<String, CmsClientProperty> parentProperties = generateParentProperties(configData.getBasePath()); String siteRoot = cms.getRequestContext().getSiteRoot(); String exportRfsPrefix = OpenCms.getStaticExportManager().getDefaultRfsPrefix(); CmsSite site = OpenCms.getSiteManager().getSiteForSiteRoot(siteRoot); boolean isSecure = site.hasSecureServer(); String parentSitemap = null; if (configData.getBasePath() != null) { CmsADEConfigData parentConfigData = OpenCms.getADEManager().lookupConfiguration( cms, CmsResource.getParentFolder(configData.getBasePath())); parentSitemap = parentConfigData.getBasePath(); if (parentSitemap != null) { parentSitemap = cms.getRequestContext().removeSiteRoot(parentSitemap); } } String noEdit = ""; CmsNewResourceInfo defaultNewInfo = null; List<CmsNewResourceInfo> newResourceInfos = null; CmsDetailPageTable detailPages = null; List<CmsNewResourceInfo> resourceTypeInfos = null; boolean canEditDetailPages = false; boolean isOnlineProject = CmsProject.isOnlineProject(cms.getRequestContext().getCurrentProject().getUuid()); Locale locale = CmsLocaleManager.getDefaultLocale(); try { String basePath = configData.getBasePath(); CmsObject rootCms = OpenCms.initCmsObject(cms); rootCms.getRequestContext().setSiteRoot(""); CmsResource baseDir = rootCms.readResource(basePath, CmsResourceFilter.ONLY_VISIBLE); OpenCms.getLocaleManager(); locale = CmsLocaleManager.getMainLocale(cms, baseDir); } catch (CmsException e) { LOG.warn(e.getLocalizedMessage(), e); } detailPages = new CmsDetailPageTable(configData.getAllDetailPages()); if (!isOnlineProject) { newResourceInfos = getNewResourceInfos(cms, configData, locale); CmsResource modelResource = null; if (configData.getDefaultModelPage() != null) { if (cms.existsResource(configData.getDefaultModelPage().getResource().getStructureId())) { modelResource = configData.getDefaultModelPage().getResource(); } else { try { modelResource = cms.readResource( cms.getSitePath(configData.getDefaultModelPage().getResource()), CmsResourceFilter.ONLY_VISIBLE); } catch (CmsException e) { LOG.warn(e.getLocalizedMessage(), e); } } } if ((modelResource == null) && !newResourceInfos.isEmpty()) { try { modelResource = cms.readResource(newResourceInfos.get(0).getCopyResourceId()); } catch (CmsException e) { LOG.warn(e.getLocalizedMessage(), e); } } if (modelResource != null) { resourceTypeInfos = getResourceTypeInfos( getCmsObject(), configData.getResourceTypes(), configData.getFunctionReferences(), modelResource, locale); try { defaultNewInfo = createNewResourceInfo(cms, modelResource, locale); } catch (CmsException e) { LOG.warn(e.getLocalizedMessage(), e); } } canEditDetailPages = !(configData.isModuleConfiguration()); } if (isOnlineProject) { noEdit = Messages.get().getBundle(getWorkplaceLocale()).key(Messages.GUI_SITEMAP_NO_EDIT_ONLINE_0); } List<String> allPropNames = getPropertyNames(cms); String returnCode = getRequest().getParameter(CmsCoreData.PARAM_RETURNCODE); if (!isValidReturnCode(returnCode)) { returnCode = null; } cms.getRequestContext().getSiteRoot(); result = new CmsSitemapData( (new CmsTemplateFinder(cms)).getTemplates(), propertyConfig, getClipboardData(), parentProperties, allPropNames, exportRfsPrefix, isSecure, noEdit, isDisplayToolbar(getRequest()), defaultNewInfo, newResourceInfos, createResourceTypeInfo(OpenCms.getResourceManager().getResourceType(RECOURCE_TYPE_NAME_REDIRECT), null), getSitemapInfo(configData.getBasePath()), parentSitemap, getRootEntry(configData.getBasePath(), openPath), openPath, 30, detailPages, resourceTypeInfos, returnCode, canEditDetailPages); } catch (Throwable e) { error(e); } return result; } /** * @see org.opencms.ade.sitemap.shared.rpc.I_CmsSitemapService#save(java.lang.String, org.opencms.ade.sitemap.shared.CmsSitemapChange) */ public CmsSitemapChange save(String entryPoint, CmsSitemapChange change) throws CmsRpcException { CmsSitemapChange result = null; try { result = saveInternal(entryPoint, change); } catch (Exception e) { error(e); } return result; } /** * @see org.opencms.ade.sitemap.shared.rpc.I_CmsSitemapService#saveSync(java.lang.String, org.opencms.ade.sitemap.shared.CmsSitemapChange) */ public CmsSitemapChange saveSync(String entryPoint, CmsSitemapChange change) throws CmsRpcException { return save(entryPoint, change); } /** * Creates a "broken link" bean based on a resource.<p> * * @param resource the resource * * @return the "broken link" bean with the data from the resource * * @throws CmsException if something goes wrong */ protected CmsBrokenLinkBean createSitemapBrokenLinkBean(CmsResource resource) throws CmsException { CmsObject cms = getCmsObject(); CmsProperty titleProp = cms.readPropertyObject(resource, CmsPropertyDefinition.PROPERTY_TITLE, true); String defaultTitle = ""; String title = titleProp.getValue(defaultTitle); String path = cms.getSitePath(resource); String subtitle = path; return new CmsBrokenLinkBean(title, subtitle); } /** * Locks the given resource with a temporary, if not already locked by the current user. * Will throw an exception if the resource could not be locked for the current user.<p> * * @param resource the resource to lock * * @return the assigned lock * * @throws CmsException if the resource could not be locked */ protected LockInfo ensureLockAndGetInfo(CmsResource resource) throws CmsException { CmsObject cms = getCmsObject(); boolean justLocked = false; List<CmsResource> blockingResources = cms.getBlockingLockedResources(resource); if ((blockingResources != null) && !blockingResources.isEmpty()) { throw new CmsException(org.opencms.gwt.Messages.get().container( org.opencms.gwt.Messages.ERR_RESOURCE_HAS_BLOCKING_LOCKED_CHILDREN_1, cms.getSitePath(resource))); } CmsUser user = cms.getRequestContext().getCurrentUser(); CmsLock lock = cms.getLock(resource); if (!lock.isOwnedBy(user)) { cms.lockResourceTemporary(resource); lock = cms.getLock(resource); justLocked = true; } else if (!lock.isOwnedInProjectBy(user, cms.getRequestContext().getCurrentProject())) { cms.changeLock(resource); lock = cms.getLock(resource); justLocked = true; } return new LockInfo(lock, justLocked); } /** * Internal method for saving a sitemap.<p> * * @param entryPoint the URI of the sitemap to save * @param change the change to save * * @return list of changed sitemap entries * * @throws CmsException */ protected CmsSitemapChange saveInternal(String entryPoint, CmsSitemapChange change) throws CmsException { ensureSession(); switch (change.getChangeType()) { case clipboardOnly: // do nothing break; case remove: change = removeEntryFromNavigation(change); break; case undelete: change = undelete(change); break; default: change = applyChange(entryPoint, change); } setClipboardData(change.getClipBoardData()); return change; } /** * Converts a sequence of properties to a map of client-side property beans.<p> * * @param props the sequence of properties * @param preserveOrigin if true, the origin of the properties should be copied to the client properties * * @return the map of client properties * */ Map<String, CmsClientProperty> createClientProperties(Iterable<CmsProperty> props, boolean preserveOrigin) { Map<String, CmsClientProperty> result = new HashMap<String, CmsClientProperty>(); for (CmsProperty prop : props) { CmsClientProperty clientProp = createClientProperty(prop, preserveOrigin); result.put(prop.getName(), clientProp); } return result; } /** * Removes unnecessary locales from a container page.<p> * * @param containerPage the container page which should be changed * @param localeRes the resource used to determine the locale * * @throws CmsException if something goes wrong */ void ensureSingleLocale(CmsXmlContainerPage containerPage, CmsResource localeRes) throws CmsException { CmsObject cms = getCmsObject(); Locale mainLocale = CmsLocaleManager.getMainLocale(cms, localeRes); OpenCms.getLocaleManager(); Locale defaultLocale = CmsLocaleManager.getDefaultLocale(); if (containerPage.hasLocale(mainLocale)) { removeAllLocalesExcept(containerPage, mainLocale); // remove other locales } else if (containerPage.hasLocale(defaultLocale)) { containerPage.copyLocale(defaultLocale, mainLocale); removeAllLocalesExcept(containerPage, mainLocale); } else if (containerPage.getLocales().size() > 0) { containerPage.copyLocale(containerPage.getLocales().get(0), mainLocale); removeAllLocalesExcept(containerPage, mainLocale); } else { containerPage.addLocale(cms, mainLocale); } } /** * Gets the properties of a resource as a map of client properties.<p> * * @param cms the CMS context to use * @param res the resource whose properties to read * @param search true if the inherited properties should be read * * @return the client properties as a map * * @throws CmsException if something goes wrong */ Map<String, CmsClientProperty> getClientProperties(CmsObject cms, CmsResource res, boolean search) throws CmsException { List<CmsProperty> props = cms.readPropertyObjects(res, search); Map<String, CmsClientProperty> result = createClientProperties(props, false); return result; } /** * Adds a function detail element to a container page.<p> * * @param cms the current CMS context * @param page the container page which should be changed * @param containerName the name of the container which should be used for function detail elements * @param elementId the structure id of the element to add * @param formatterId the structure id of the formatter for the element * * @throws CmsException if something goes wrong */ private void addFunctionDetailElement( CmsObject cms, CmsXmlContainerPage page, String containerName, CmsUUID elementId, CmsUUID formatterId) throws CmsException { List<Locale> pageLocales = page.getLocales(); for (Locale locale : pageLocales) { CmsContainerPageBean bean = page.getContainerPage(cms, locale); List<CmsContainerBean> containerBeans = new ArrayList<CmsContainerBean>(); Collection<CmsContainerBean> originalContainers = bean.getContainers().values(); if ((containerName == null) && !originalContainers.isEmpty()) { CmsContainerBean firstContainer = originalContainers.iterator().next(); containerName = firstContainer.getName(); } boolean foundContainer = false; for (CmsContainerBean cntBean : originalContainers) { boolean isDetailTarget = cntBean.getName().equals(containerName); if (isDetailTarget && !foundContainer) { foundContainer = true; List<CmsContainerElementBean> newElems = new ArrayList<CmsContainerElementBean>(); newElems.addAll(cntBean.getElements()); CmsContainerElementBean newElement = new CmsContainerElementBean( elementId, formatterId, new HashMap<String, String>(), false); newElems.add(0, newElement); CmsContainerBean newCntBean = new CmsContainerBean(cntBean.getName(), cntBean.getType(), newElems); containerBeans.add(newCntBean); } else { containerBeans.add(cntBean); } } if (!foundContainer) { throw new CmsException(Messages.get().container( Messages.ERR_NO_FUNCTION_DETAIL_CONTAINER_1, page.getFile().getRootPath())); } CmsContainerPageBean bean2 = new CmsContainerPageBean(locale, new ArrayList<CmsContainerBean>( containerBeans)); page.writeContainerPage(cms, locale, bean2); } } /** * Applys the given change to the VFS.<p> * * @param entryPoint the sitemap entry-point * @param change the change * * @return the updated entry * * @throws CmsException if something goes wrong */ private CmsSitemapChange applyChange(String entryPoint, CmsSitemapChange change) throws CmsException { CmsObject cms = getCmsObject(); CmsResource configFile = null; // lock the config file first, to avoid half done changes if (change.hasDetailPageInfos()) { CmsADEConfigData configData = OpenCms.getADEManager().lookupConfiguration( cms, cms.getRequestContext().addSiteRoot(entryPoint)); if (!configData.isModuleConfiguration() && (configData.getResource() != null)) { configFile = configData.getResource(); } if (configFile != null) { ensureLock(configFile); } } if (change.isNew()) { CmsClientSitemapEntry newEntry = createNewEntry(entryPoint, change); change.setUpdatedEntry(newEntry); change.setEntryId(newEntry.getId()); } else if (change.getChangeType() == ChangeType.delete) { delete(change); } else if (change.getEntryId() != null) { modifyEntry(change); } if (change.hasDetailPageInfos() && (configFile != null)) { saveDetailPages(change.getDetailPageInfos(), configFile, change.getEntryId()); tryUnlock(configFile); } return change; } /** * Changes the navigation for a moved entry and its neighbors.<p> * * @param change the sitemap change * @param entryFolder the moved entry * * @throws CmsException if something goes wrong */ private void applyNavigationChanges(CmsSitemapChange change, CmsResource entryFolder) throws CmsException { CmsObject cms = getCmsObject(); String parentPath = null; if (change.hasNewParent()) { CmsResource parent = cms.readResource(change.getParentId()); parentPath = cms.getSitePath(parent); } else { parentPath = CmsResource.getParentFolder(cms.getSitePath(entryFolder)); } List<CmsJspNavElement> navElements = getNavBuilder().getNavigationForFolder( parentPath, true, CmsResourceFilter.ONLY_VISIBLE); CmsSitemapNavPosCalculator npc = new CmsSitemapNavPosCalculator(navElements, entryFolder, change.getPosition()); List<CmsJspNavElement> navs = npc.getNavigationChanges(); List<CmsResource> needToUnlock = new ArrayList<CmsResource>(); try { for (CmsJspNavElement nav : navs) { LockInfo lockInfo = ensureLockAndGetInfo(nav.getResource()); if (!nav.getResource().equals(entryFolder) && lockInfo.wasJustLocked()) { needToUnlock.add(nav.getResource()); } } for (CmsJspNavElement nav : navs) { CmsProperty property = new CmsProperty( CmsPropertyDefinition.PROPERTY_NAVPOS, "" + nav.getNavPosition(), null); cms.writePropertyObject(cms.getSitePath(nav.getResource()), property); } } finally { for (CmsResource lockedRes : needToUnlock) { try { cms.unlockResource(lockedRes); } catch (CmsException e) { // we catch this because we still want to unlock the other resources LOG.error(e.getLocalizedMessage(), e); } } } } /** * Creates a client property bean from a server-side property.<p> * * @param prop the property from which to create the client property * @param preserveOrigin if true, the origin will be copied into the new object * * @return the new client property */ private CmsClientProperty createClientProperty(CmsProperty prop, boolean preserveOrigin) { CmsClientProperty result = new CmsClientProperty( prop.getName(), prop.getStructureValue(), prop.getResourceValue()); if (preserveOrigin) { result.setOrigin(prop.getOrigin()); } return result; } /** * Creates a new page in navigation.<p> * * @param entryPoint the site-map entry-point * @param change the new change * * @return the updated entry * * @throws CmsException if something goes wrong */ private CmsClientSitemapEntry createNewEntry(String entryPoint, CmsSitemapChange change) throws CmsException { CmsObject cms = getCmsObject(); CmsClientSitemapEntry newEntry = null; if (change.getParentId() != null) { CmsResource parentFolder = cms.readResource(change.getParentId(), CmsResourceFilter.ONLY_VISIBLE); String entryPath = ""; CmsResource entryFolder = null; CmsResource newRes = null; byte[] content = null; List<CmsProperty> properties = Collections.emptyList(); CmsResource copyPage = null; if (change.getNewCopyResourceId() != null) { copyPage = cms.readResource(change.getNewCopyResourceId(), CmsResourceFilter.ONLY_VISIBLE); content = cms.readFile(copyPage).getContents(); properties = cms.readPropertyObjects(copyPage, false); } if (isRedirectType(change.getNewResourceTypeId())) { entryPath = CmsStringUtil.joinPaths(cms.getSitePath(parentFolder), change.getName()); newRes = cms.createResource( entryPath, change.getNewResourceTypeId(), null, Collections.singletonList(new CmsProperty( CmsPropertyDefinition.PROPERTY_TITLE, change.getName(), null))); cms.writePropertyObjects(newRes, generateInheritProperties(change, newRes)); applyNavigationChanges(change, newRes); } else { String entryFolderPath = CmsStringUtil.joinPaths(cms.getSitePath(parentFolder), change.getName() + "/"); boolean idWasNull = change.getEntryId() == null; // we don'T really need to create a folder object here anymore. if (idWasNull) { // need this for calculateNavPosition, even though the id will get overwritten change.setEntryId(new CmsUUID()); } boolean isFunctionDetail = false; if (change.getCreateParameter() != null) { if (CmsUUID.isValidUUID(change.getCreateParameter())) { isFunctionDetail = true; } } entryFolder = new CmsResource( change.getEntryId(), new CmsUUID(), entryFolderPath, CmsResourceTypeFolder.getStaticTypeId(), true, 0, cms.getRequestContext().getCurrentProject().getUuid(), CmsResource.STATE_NEW, System.currentTimeMillis(), cms.getRequestContext().getCurrentUser().getId(), System.currentTimeMillis(), cms.getRequestContext().getCurrentUser().getId(), CmsResource.DATE_RELEASED_DEFAULT, CmsResource.DATE_EXPIRED_DEFAULT, 1, 0, System.currentTimeMillis(), 0); List<CmsProperty> folderProperties = generateInheritProperties(change, entryFolder); entryFolder = cms.createResource( entryFolderPath, OpenCms.getResourceManager().getResourceType(CmsResourceTypeFolder.getStaticTypeName()).getTypeId(), null, folderProperties); if (idWasNull) { change.setEntryId(entryFolder.getStructureId()); } applyNavigationChanges(change, entryFolder); entryPath = CmsStringUtil.joinPaths(entryFolderPath, "index.html"); boolean isContainerPage = change.getNewResourceTypeId() == CmsResourceTypeXmlContainerPage.getContainerPageTypeIdSafely(); if (isContainerPage && (copyPage != null)) { // do *NOT* get this from the cache, because we perform some destructive operation on the XML content CmsXmlContainerPage page = CmsXmlContainerPageFactory.unmarshal( cms, cms.readFile(copyPage), true, true); ensureSingleLocale(page, entryFolder); if (isFunctionDetail) { String functionDetailContainer = getFunctionDetailContainerName(parentFolder); CmsUUID functionStructureId = new CmsUUID(change.getCreateParameter()); CmsResource functionFormatter = cms.readResource( CmsXmlDynamicFunctionHandler.FORMATTER_PATH, CmsResourceFilter.ONLY_VISIBLE); addFunctionDetailElement( cms, page, functionDetailContainer, functionStructureId, functionFormatter.getStructureId()); } content = page.marshal(); } newRes = cms.createResource(entryPath, change.getNewResourceTypeId(), content, properties); cms.writePropertyObjects(newRes, generateOwnProperties(change)); } if (entryFolder != null) { tryUnlock(entryFolder); String sitePath = cms.getSitePath(entryFolder); newEntry = toClientEntry( getNavBuilder().getNavigationForResource(sitePath, CmsResourceFilter.ONLY_VISIBLE), false); newEntry.setSubEntries(getChildren(sitePath, 1, null), null); newEntry.setChildrenLoadedInitially(true); } if (newRes != null) { tryUnlock(newRes); } if (newEntry == null) { newEntry = toClientEntry( getNavBuilder().getNavigationForResource(cms.getSitePath(newRes), CmsResourceFilter.ONLY_VISIBLE), false); } // mark position as not set newEntry.setPosition(-1); newEntry.setNew(true); change.getClipBoardData().getModifications().remove(null); change.getClipBoardData().getModifications().put(newEntry.getId(), newEntry); } return newEntry; } /** * Creates a new resource info to a given model page resource.<p> * * @param cms the current CMS context * @param modelResource the model page resource * @param locale the locale used for retrieving descriptions/titles * * @return the new resource info * * @throws CmsException if something goes wrong */ private CmsNewResourceInfo createNewResourceInfo(CmsObject cms, CmsResource modelResource, Locale locale) throws CmsException { // if model page got overwritten by another resource, reread from site path if (!cms.existsResource(modelResource.getStructureId(), CmsResourceFilter.ONLY_VISIBLE)) { modelResource = cms.readResource(cms.getSitePath(modelResource), CmsResourceFilter.ONLY_VISIBLE); } int typeId = modelResource.getTypeId(); String name = OpenCms.getResourceManager().getResourceType(typeId).getTypeName(); String title = cms.readPropertyObject(modelResource, CmsPropertyDefinition.PROPERTY_TITLE, false).getValue(); String description = cms.readPropertyObject(modelResource, CmsPropertyDefinition.PROPERTY_DESCRIPTION, false).getValue(); try { CmsGallerySearchResult result = CmsGallerySearch.searchById(cms, modelResource.getStructureId(), locale); if (!CmsStringUtil.isEmptyOrWhitespaceOnly(result.getTitle())) { title = result.getTitle(); } if (!CmsStringUtil.isEmptyOrWhitespaceOnly(result.getDescription())) { description = result.getDescription(); } } catch (CmsException e) { LOG.warn(e.getLocalizedMessage(), e); } boolean editable = false; try { CmsResource freshModelResource = cms.readResource( modelResource.getStructureId(), CmsResourceFilter.ONLY_VISIBLE); editable = cms.hasPermissions( freshModelResource, CmsPermissionSet.ACCESS_WRITE, false, CmsResourceFilter.DEFAULT); } catch (CmsException e) { LOG.warn(e.getLocalizedMessage(), e); } CmsNewResourceInfo info = new CmsNewResourceInfo( typeId, name, title, description, modelResource.getStructureId(), editable, description); Float navpos = null; try { CmsProperty navposProp = cms.readPropertyObject(modelResource, CmsPropertyDefinition.PROPERTY_NAVPOS, true); String navposStr = navposProp.getValue(); if (navposStr != null) { try { navpos = Float.valueOf(navposStr); } catch (NumberFormatException e) { // noop } } } catch (CmsException e) { LOG.warn(e.getLocalizedMessage(), e); } info.setNavPos(navpos); info.setDate(CmsDateUtil.getDate( new Date(modelResource.getDateLastModified()), DateFormat.LONG, getWorkplaceLocale())); info.setVfsPath(modelResource.getRootPath()); return info; } /** * Creates a resource type info bean for a given resource type.<p> * * @param resType the resource type * @param copyResource the structure id of the copy resource * * @return the resource type info bean */ private CmsNewResourceInfo createResourceTypeInfo(I_CmsResourceType resType, CmsResource copyResource) { String name = resType.getTypeName(); Locale locale = getWorkplaceLocale(); String subtitle = CmsWorkplaceMessages.getResourceTypeDescription(locale, name); if (CmsStringUtil.isEmptyOrWhitespaceOnly(subtitle)) { subtitle = CmsWorkplaceMessages.getResourceTypeName(locale, name); } if (copyResource != null) { return new CmsNewResourceInfo( copyResource.getTypeId(), name, CmsWorkplaceMessages.getResourceTypeName(locale, name), CmsWorkplaceMessages.getResourceTypeDescription(locale, name), copyResource.getStructureId(), false, subtitle); } else { return new CmsNewResourceInfo(resType.getTypeId(), name, CmsWorkplaceMessages.getResourceTypeName( locale, name), CmsWorkplaceMessages.getResourceTypeDescription(locale, name), null, false, subtitle); } } /** * Deletes a resource according to the change data.<p> * * @param change the change data * * @return CmsClientSitemapEntry always null * * * @throws CmsException if something goes wrong */ private CmsClientSitemapEntry delete(CmsSitemapChange change) throws CmsException { CmsObject cms = getCmsObject(); CmsResource resource = cms.readResource(change.getEntryId(), CmsResourceFilter.ONLY_VISIBLE); ensureLock(resource); cms.deleteResource(cms.getSitePath(resource), CmsResource.DELETE_PRESERVE_SIBLINGS); tryUnlock(resource); return null; } /** * Generates a client side lock info object representing the current lock state of the given resource.<p> * * @param resource the resource * * @return the client lock * * @throws CmsException if something goes wrong */ private CmsClientLock generateClientLock(CmsResource resource) throws CmsException { CmsObject cms = getCmsObject(); CmsLock lock = cms.getLock(resource); CmsClientLock clientLock = new CmsClientLock(); clientLock.setLockType(CmsClientLock.LockType.valueOf(lock.getType().getMode())); CmsUUID ownerId = lock.getUserId(); if (!lock.isUnlocked() && (ownerId != null)) { clientLock.setLockOwner(cms.readUser(ownerId).getDisplayName(cms, cms.getRequestContext().getLocale())); clientLock.setOwnedByUser(cms.getRequestContext().getCurrentUser().getId().equals(ownerId)); } return clientLock; } /** * Generates a list of property object to save to the sitemap entry folder to apply the given change.<p> * * @param change the change * @param entryFolder the entry folder * * @return the property objects */ private List<CmsProperty> generateInheritProperties(CmsSitemapChange change, CmsResource entryFolder) { List<CmsProperty> result = new ArrayList<CmsProperty>(); Map<String, CmsClientProperty> clientProps = change.getOwnInternalProperties(); if (clientProps != null) { for (CmsClientProperty clientProp : clientProps.values()) { CmsProperty prop = new CmsProperty( clientProp.getName(), clientProp.getStructureValue(), clientProp.getResourceValue()); result.add(prop); } } result.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_TITLE, change.getName(), null)); return result; } /** * Generates a list of property object to save to the sitemap entry resource to apply the given change.<p> * * @param change the change * * @return the property objects */ private List<CmsProperty> generateOwnProperties(CmsSitemapChange change) { List<CmsProperty> result = new ArrayList<CmsProperty>(); Map<String, CmsClientProperty> clientProps = change.getDefaultFileProperties(); if (clientProps != null) { for (CmsClientProperty clientProp : clientProps.values()) { CmsProperty prop = new CmsProperty( clientProp.getName(), clientProp.getStructureValue(), clientProp.getResourceValue()); result.add(prop); } } result.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_TITLE, change.getName(), null)); return result; } /** * Generates a list of property values inherited to the site-map root entry.<p> * * @param rootPath the root entry name * * @return the list of property values inherited to the site-map root entry * * @throws CmsException if something goes wrong reading the properties */ private Map<String, CmsClientProperty> generateParentProperties(String rootPath) throws CmsException { CmsObject cms = getCmsObject(); if (rootPath == null) { rootPath = cms.getRequestContext().addSiteRoot("/"); } CmsObject rootCms = OpenCms.initCmsObject(cms); rootCms.getRequestContext().setSiteRoot(""); String parentRootPath = CmsResource.getParentFolder(rootPath); Map<String, CmsClientProperty> result = new HashMap<String, CmsClientProperty>(); if (parentRootPath != null) { List<CmsProperty> props = rootCms.readPropertyObjects(parentRootPath, true); for (CmsProperty prop : props) { CmsClientProperty clientProp = createClientProperty(prop, true); result.put(clientProp.getName(), clientProp); } } return result; } /** * Returns the sitemap children for the given path with all descendants up to the given level or to the given target path, ie. * <dl><dt>levels=1 </dt><dd>only children</dd><dt>levels=2</dt><dd>children and great children</dd></dl> * and so on.<p> * * @param root the site relative root * @param levels the levels to recurse * @param targetPath the target path * * @return the sitemap children * * @throws CmsException if something goes wrong */ private List<CmsClientSitemapEntry> getChildren(String root, int levels, String targetPath) throws CmsException { List<CmsClientSitemapEntry> children = new ArrayList<CmsClientSitemapEntry>(); int i = 0; for (CmsJspNavElement navElement : getNavBuilder().getNavigationForFolder( root, true, CmsResourceFilter.ONLY_VISIBLE)) { CmsClientSitemapEntry child = toClientEntry(navElement, false); if (child != null) { child.setPosition(i); children.add(child); int nextLevels = levels; if ((nextLevels == 1) && (targetPath != null) && targetPath.startsWith(child.getSitePath())) { nextLevels = 2; } if (child.isFolderType() && ((nextLevels > 1) || (nextLevels == -1)) && !isSubSitemap(navElement)) { child.setSubEntries(getChildren(child.getSitePath(), nextLevels - 1, targetPath), null); child.setChildrenLoadedInitially(true); } i++; } } return children; } /** * Returns the clipboard data from the current user.<p> * * @return the clipboard data */ private CmsSitemapClipboardData getClipboardData() { CmsSitemapClipboardData result = new CmsSitemapClipboardData(); result.setModifications(getModifiedList()); result.setDeletions(getDeletedList()); return result; } /** * Returns the deleted list from the current user.<p> * * @return the deleted list */ private LinkedHashMap<CmsUUID, CmsClientSitemapEntry> getDeletedList() { CmsObject cms = getCmsObject(); CmsUser user = cms.getRequestContext().getCurrentUser(); Object obj = user.getAdditionalInfo(ADDINFO_ADE_DELETED_LIST); LinkedHashMap<CmsUUID, CmsClientSitemapEntry> result = new LinkedHashMap<CmsUUID, CmsClientSitemapEntry>(); if (obj instanceof String) { try { JSONArray array = new JSONArray((String)obj); for (int i = 0; i < array.length(); i++) { try { CmsUUID delId = new CmsUUID(array.getString(i)); CmsResource res = cms.readResource(delId, CmsResourceFilter.ALL); if (res.getState().isDeleted()) { // make sure resource is still deleted CmsClientSitemapEntry delEntry = new CmsClientSitemapEntry(); delEntry.setSitePath(cms.getSitePath(res)); delEntry.setOwnProperties(getClientProperties(cms, res, false)); delEntry.setName(res.getName()); delEntry.setVfsPath(cms.getSitePath(res)); delEntry.setEntryType(res.isFolder() ? EntryType.folder : isRedirectType(res.getTypeId()) ? EntryType.redirect : EntryType.leaf); delEntry.setId(delId); result.put(delId, delEntry); } } catch (Throwable e) { // should never happen, catches wrong or no longer existing values LOG.warn(e.getLocalizedMessage()); } } } catch (Throwable e) { // should never happen, catches json parsing LOG.warn(e.getLocalizedMessage()); } } return result; } /** * Gets the type id for entry point folders.<p> * * @return the type id for entry point folders * * @throws CmsException if something goes wrong */ private int getEntryPointType() throws CmsException { return OpenCms.getResourceManager().getResourceType(CmsResourceTypeFolderExtended.TYPE_ENTRY_POINT).getTypeId(); } /** * Gets the container name for function detail elements depending on the parent folder.<p> * * @param parent the parent folder * @return the name of the function detail container */ private String getFunctionDetailContainerName(CmsResource parent) { try { CmsObject cms = getCmsObject(); CmsObject rootCms = OpenCms.initCmsObject(cms); rootCms.getRequestContext().setSiteRoot(""); CmsProperty templateProp = cms.readPropertyObject(parent, CmsPropertyDefinition.PROPERTY_TEMPLATE, true); String templateVal = templateProp.getValue(); if (templateVal == null) { return null; } CmsResource templateRes; try { templateRes = cms.readResource(templateVal, CmsResourceFilter.ONLY_VISIBLE); } catch (CmsVfsResourceNotFoundException e) { templateRes = rootCms.readResource(templateVal, CmsResourceFilter.ONLY_VISIBLE); } CmsProperty containerInfoProp = cms.readPropertyObject( templateRes, CmsPropertyDefinition.PROPERTY_CONTAINER_INFO, true); String containerInfo = containerInfoProp.getValue() == null ? "" : containerInfoProp.getValue(); Map<String, String> attrs = CmsStringUtil.splitAsMap(containerInfo, "|", "="); String functionDetailContainerName = attrs.get(KEY_FUNCTION_DETAIL); return functionDetailContainerName; } catch (CmsException e) { LOG.warn(e.getLocalizedMessage(), e); return null; } } /** * Returns the modified list from the current user.<p> * * @return the modified list */ private LinkedHashMap<CmsUUID, CmsClientSitemapEntry> getModifiedList() { CmsObject cms = getCmsObject(); CmsUser user = cms.getRequestContext().getCurrentUser(); Object obj = user.getAdditionalInfo(ADDINFO_ADE_MODIFIED_LIST); LinkedHashMap<CmsUUID, CmsClientSitemapEntry> result = new LinkedHashMap<CmsUUID, CmsClientSitemapEntry>(); if (obj instanceof String) { try { JSONArray array = new JSONArray((String)obj); for (int i = 0; i < array.length(); i++) { try { CmsUUID modId = new CmsUUID(array.getString(i)); CmsResource res = cms.readResource(modId, CmsResourceFilter.ONLY_VISIBLE); String sitePath = cms.getSitePath(res); CmsJspNavElement navEntry = getNavBuilder().getNavigationForResource( sitePath, CmsResourceFilter.ONLY_VISIBLE); if (navEntry.isInNavigation()) { CmsClientSitemapEntry modEntry = toClientEntry(navEntry, false); result.put(modId, modEntry); } } catch (Throwable e) { // should never happen, catches wrong or no longer existing values LOG.warn(e.getLocalizedMessage()); } } } catch (Throwable e) { // should never happen, catches json parsing LOG.warn(e.getLocalizedMessage()); } } return result; } /** * Returns a navigation builder reference.<p> * * @return the navigation builder */ private CmsJspNavBuilder getNavBuilder() { if (m_navBuilder == null) { m_navBuilder = new CmsJspNavBuilder(getCmsObject()); } return m_navBuilder; } /** * Returns the new resource infos.<p> * * @param cms the current CMS context * @param configData the configuration data from which the new resource infos should be read * @param locale locale used for retrieving descriptions/titles * * @return the new resource infos */ private List<CmsNewResourceInfo> getNewResourceInfos(CmsObject cms, CmsADEConfigData configData, Locale locale) { List<CmsNewResourceInfo> result = new ArrayList<CmsNewResourceInfo>(); for (CmsModelPageConfig modelConfig : configData.getModelPages()) { try { result.add(createNewResourceInfo(cms, modelConfig.getResource(), locale)); } catch (CmsException e) { LOG.debug(e.getLocalizedMessage(), e); } } Collections.sort(result, new Comparator<CmsNewResourceInfo>() { public int compare(CmsNewResourceInfo a, CmsNewResourceInfo b) { return ComparisonChain.start().compare(a.getNavPos(), b.getNavPos(), Ordering.natural().nullsLast()).result(); } }); return result; } /** * Gets the names of all available properties.<p> * * @param cms the CMS context * * @return the list of all property names * * @throws CmsException */ private List<String> getPropertyNames(CmsObject cms) throws CmsException { List<CmsPropertyDefinition> propDefs = cms.readAllPropertyDefinitions(); List<String> result = new ArrayList<String>(); for (CmsPropertyDefinition propDef : propDefs) { result.add(propDef.getName()); } return result; } /** * Gets the resource type info beans for types for which new detail pages can be created.<p> * * @param cms the current CMS context * @param resourceTypeConfigs the resource type configurations * @param functionReferences the function references * @param modelResource the model resource * @param locale the locale used for retrieving descriptions/titles * * @return the resource type info beans for types for which new detail pages can be created */ private List<CmsNewResourceInfo> getResourceTypeInfos( CmsObject cms, List<CmsResourceTypeConfig> resourceTypeConfigs, List<CmsFunctionReference> functionReferences, CmsResource modelResource, Locale locale) { List<CmsNewResourceInfo> result = new ArrayList<CmsNewResourceInfo>(); for (CmsResourceTypeConfig typeConfig : resourceTypeConfigs) { if (typeConfig.isDetailPagesDisabled()) { continue; } String typeName = typeConfig.getTypeName(); try { I_CmsResourceType resourceType = OpenCms.getResourceManager().getResourceType(typeName); result.add(createResourceTypeInfo(resourceType, modelResource)); } catch (CmsLoaderException e) { LOG.error(e.getLocalizedMessage(), e); } } for (CmsFunctionReference functionRef : functionReferences) { try { CmsResource functionRes = cms.readResource(functionRef.getStructureId()); String description = cms.readPropertyObject( functionRes, CmsPropertyDefinition.PROPERTY_DESCRIPTION, false).getValue(); String subtitle = description; try { CmsGallerySearchResult searchResult = CmsGallerySearch.searchById( cms, functionRef.getStructureId(), getWorkplaceLocale()); subtitle = searchResult.getDescription(); } catch (CmsException e) { LOG.warn(e.getLocalizedMessage(), e); } CmsNewResourceInfo info = new CmsNewResourceInfo( modelResource.getTypeId(), CmsDetailPageInfo.FUNCTION_PREFIX + functionRef.getName(), functionRef.getName(), description, modelResource.getStructureId(), false, subtitle); info.setCreateParameter(functionRef.getStructureId().toString()); info.setIsFunction(true); result.add(info); } catch (CmsVfsResourceNotFoundException e) { LOG.warn(e.getLocalizedMessage(), e); } catch (CmsException e) { LOG.error(e.getLocalizedMessage(), e); } } return result; } /** * Reeds the site root entry.<p> * * @param rootPath the root path of the sitemap root * @param targetPath the target path to open * * @return the site root entry * * @throws CmsSecurityException in case of insufficient permissions * @throws CmsException if something goes wrong */ private CmsClientSitemapEntry getRootEntry(String rootPath, String targetPath) throws CmsSecurityException, CmsException { String sitePath = "/"; if (rootPath != null) { sitePath = getCmsObject().getRequestContext().removeSiteRoot(rootPath); } CmsJspNavElement navElement = getNavBuilder().getNavigationForResource(sitePath, CmsResourceFilter.ONLY_VISIBLE); CmsClientSitemapEntry result = toClientEntry(navElement, true); if (result != null) { result.setPosition(0); result.setChildrenLoadedInitially(true); result.setSubEntries(getChildren(sitePath, 2, targetPath), null); } return result; } /** * Returns the sitemap info for the given base path.<p> * * @param basePath the base path * * @return the sitemap info * * @throws CmsException if something goes wrong reading the resources */ private CmsSitemapInfo getSitemapInfo(String basePath) throws CmsException { CmsObject cms = getCmsObject(); CmsResource baseFolder = null; if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(basePath)) { baseFolder = cms.readResource( cms.getRequestContext().removeSiteRoot(basePath), CmsResourceFilter.ONLY_VISIBLE); } else { // in case of an empty base path, use base folder of the current site basePath = "/"; baseFolder = cms.readResource("/"); } CmsResource defaultFile = cms.readDefaultFile(baseFolder, CmsResourceFilter.ONLY_VISIBLE); String title = cms.readPropertyObject(baseFolder, CmsPropertyDefinition.PROPERTY_TITLE, false).getValue(); if (CmsStringUtil.isEmptyOrWhitespaceOnly(title) && (defaultFile != null)) { title = cms.readPropertyObject(defaultFile, CmsPropertyDefinition.PROPERTY_TITLE, false).getValue(); } String description = cms.readPropertyObject(baseFolder, CmsPropertyDefinition.PROPERTY_DESCRIPTION, false).getValue(); if (CmsStringUtil.isEmptyOrWhitespaceOnly(description) && (defaultFile != null)) { description = cms.readPropertyObject(defaultFile, CmsPropertyDefinition.PROPERTY_DESCRIPTION, false).getValue(); } return new CmsSitemapInfo( cms.getRequestContext().getCurrentProject().getName(), description, OpenCms.getLocaleManager().getDefaultLocale(cms, baseFolder).toString(), OpenCms.getSiteManager().getCurrentSite(cms).getUrl() + OpenCms.getLinkManager().substituteLinkForUnknownTarget(cms, basePath), title); } /** * Returns the workplace locale for the current user.<p> * * @return the workplace locale */ private Locale getWorkplaceLocale() { Locale result = OpenCms.getWorkplaceManager().getWorkplaceLocale(getCmsObject()); if (result == null) { result = CmsLocaleManager.getDefaultLocale(); } if (result == null) { result = Locale.getDefault(); } return result; } /** * Checks whether the sitemap change has default file changes.<p> * * @param change a sitemap change * @return true if the change would change the default file */ private boolean hasDefaultFileChanges(CmsSitemapChange change) { return (change.getDefaultFileId() != null) && !change.isNew(); //TODO: optimize this! } /** * Checks whether the sitemap change has changes for the sitemap entry resource.<p> * * @param change the sitemap change * @return true if the change would change the original sitemap entry resource */ private boolean hasOwnChanges(CmsSitemapChange change) { return !change.isNew(); //TODO: optimize this! } /** * Checks whether a resource is a default file of a folder.<p> * * @param resource the resource to check * * @return true if the resource is the default file of a folder * * @throws CmsException if something goes wrong */ private boolean isDefaultFile(CmsResource resource) throws CmsException { CmsObject cms = getCmsObject(); if (resource.isFolder()) { return false; } CmsResource parent = cms.readResource( CmsResource.getParentFolder(cms.getSitePath(resource)), CmsResourceFilter.ONLY_VISIBLE); CmsResource defaultFile = cms.readDefaultFile(parent, CmsResourceFilter.ONLY_VISIBLE); return resource.equals(defaultFile); } /** * Checks if the toolbar should be displayed.<p> * * @param request the current request to get the default locale from * * @return <code>true</code> if the toolbar should be displayed */ private boolean isDisplayToolbar(HttpServletRequest request) { // display the toolbar by default boolean displayToolbar = true; if (CmsHistoryResourceHandler.isHistoryRequest(request)) { // we do not want to display the toolbar in case of an historical request displayToolbar = false; } return displayToolbar; } /** * Returns if the given type id matches the xml-redirect resource type.<p> * * @param typeId the resource type id * * @return <code>true</code> if the given type id matches the xml-redirect resource type */ private boolean isRedirectType(int typeId) { try { return typeId == OpenCms.getResourceManager().getResourceType(RECOURCE_TYPE_NAME_REDIRECT).getTypeId(); } catch (Exception e) { return false; } } /** * Returns if the given nav-element resembles a sub-sitemap entry-point.<p> * * @param navElement the nav-element * * @return <code>true</code> if the given nav-element resembles a sub-sitemap entry-point.<p> * @throws CmsException if something goes wrong */ private boolean isSubSitemap(CmsJspNavElement navElement) throws CmsException { return navElement.getResource().getTypeId() == getEntryPointType(); } /** * Checks if the given open path is valid.<p> * * @param cms the cms context * @param openPath the open path * * @return <code>true</code> if the given open path is valid */ private boolean isValidOpenPath(CmsObject cms, String openPath) { if (CmsStringUtil.isEmptyOrWhitespaceOnly(openPath)) { return false; } if (!cms.existsResource(openPath)) { // in case of a detail-page check the parent folder String parent = CmsResource.getParentFolder(openPath); if (CmsStringUtil.isEmptyOrWhitespaceOnly(parent) || !cms.existsResource(parent)) { return false; } } return true; } /** * Returns if the given return code is valid.<p> * * @param returnCode the return code to check * * @return <code>true</code> if the return code is valid */ private boolean isValidReturnCode(String returnCode) { if (CmsStringUtil.isEmptyOrWhitespaceOnly(returnCode)) { return false; } int pos = returnCode.indexOf(":"); if (pos > 0) { return CmsUUID.isValidUUID(returnCode.substring(0, pos)) && CmsUUID.isValidUUID(returnCode.substring(pos + 1)); } else { return CmsUUID.isValidUUID(returnCode); } } /** * Applys the given changes to the entry.<p> * * @param change the change to apply * * @throws CmsException if something goes wrong */ private void modifyEntry(CmsSitemapChange change) throws CmsException { CmsObject cms = getCmsObject(); CmsResource entryPage = null; CmsResource entryFolder = null; CmsResource ownRes = null; CmsResource defaultFileRes = null; try { // lock all resources necessary first to avoid doing changes only half way through if (hasOwnChanges(change)) { ownRes = cms.readResource(change.getEntryId(), CmsResourceFilter.ONLY_VISIBLE); ensureLock(ownRes); } if (hasDefaultFileChanges(change)) { defaultFileRes = cms.readResource(change.getDefaultFileId(), CmsResourceFilter.ONLY_VISIBLE); ensureLock(defaultFileRes); } if ((ownRes != null) && ownRes.isFolder()) { entryFolder = ownRes; } if ((ownRes != null) && ownRes.isFile()) { entryPage = ownRes; } if (defaultFileRes != null) { entryPage = defaultFileRes; } if (change.isLeafType()) { entryFolder = entryPage; } updateProperties(cms, ownRes, defaultFileRes, change.getPropertyChanges()); if (change.hasChangedPosition()) { updateNavPos(ownRes, change); } if (entryFolder != null) { if (change.hasNewParent() || change.hasChangedName()) { String destinationPath; if (change.hasNewParent()) { CmsResource futureParent = cms.readResource( change.getParentId(), CmsResourceFilter.ONLY_VISIBLE); destinationPath = CmsStringUtil.joinPaths(cms.getSitePath(futureParent), change.getName()); } else { destinationPath = CmsStringUtil.joinPaths( CmsResource.getParentFolder(cms.getSitePath(entryFolder)), change.getName()); } if (change.isLeafType() && destinationPath.endsWith("/")) { destinationPath = destinationPath.substring(0, destinationPath.length() - 1); } // only if the site-path has really changed if (!cms.getSitePath(entryFolder).equals(destinationPath)) { cms.moveResource(cms.getSitePath(entryFolder), destinationPath); } entryFolder = cms.readResource(entryFolder.getStructureId(), CmsResourceFilter.ONLY_VISIBLE); } } } finally { if (entryPage != null) { tryUnlock(entryPage); } if (entryFolder != null) { tryUnlock(entryFolder); } } } /** * Helper method for removing all locales except one from a container page.<p> * * @param page the container page to proces * @param localeToKeep the locale which should be kept * * @throws CmsXmlException if something goes wrong */ private void removeAllLocalesExcept(CmsXmlContainerPage page, Locale localeToKeep) throws CmsXmlException { List<Locale> locales = page.getLocales(); for (Locale locale : locales) { if (!locale.equals(localeToKeep)) { page.removeLocale(locale); } } } /** * Applys the given remove change.<p> * * @param change the change to apply * * @return the changed client sitemap entry or <code>null</code> * * @throws CmsException if something goes wrong */ private CmsSitemapChange removeEntryFromNavigation(CmsSitemapChange change) throws CmsException { CmsObject cms = getCmsObject(); CmsResource entryFolder = cms.readResource(change.getEntryId(), CmsResourceFilter.ONLY_VISIBLE); ensureLock(entryFolder); List<CmsProperty> properties = new ArrayList<CmsProperty>(); properties.add(new CmsProperty( CmsPropertyDefinition.PROPERTY_NAVTEXT, CmsProperty.DELETE_VALUE, CmsProperty.DELETE_VALUE)); properties.add(new CmsProperty( CmsPropertyDefinition.PROPERTY_NAVPOS, CmsProperty.DELETE_VALUE, CmsProperty.DELETE_VALUE)); cms.writePropertyObjects(cms.getSitePath(entryFolder), properties); tryUnlock(entryFolder); return change; } /** * Saves the detail page information of a sitemap to the sitemap's configuration file.<p> * * @param detailPages saves the detailpage configuration * @param resource the configuration file resource * @param newId the structure id to use for new detail page entries * * @throws CmsException */ private void saveDetailPages(List<CmsDetailPageInfo> detailPages, CmsResource resource, CmsUUID newId) throws CmsException { CmsObject cms = getCmsObject(); CmsDetailPageConfigurationWriter writer = new CmsDetailPageConfigurationWriter(cms, resource); writer.updateAndSave(detailPages, newId); } /** * Saves the given clipboard data to the session.<p> * * @param clipboardData the clipboard data to save * * @throws CmsException if something goes wrong writing the user */ private void setClipboardData(CmsSitemapClipboardData clipboardData) throws CmsException { CmsObject cms = getCmsObject(); CmsUser user = cms.getRequestContext().getCurrentUser(); if (clipboardData != null) { JSONArray modified = new JSONArray(); if (clipboardData.getModifications() != null) { for (CmsUUID id : clipboardData.getModifications().keySet()) { if (id != null) { modified.put(id.toString()); } } } user.setAdditionalInfo(ADDINFO_ADE_MODIFIED_LIST, modified.toString()); JSONArray deleted = new JSONArray(); if (clipboardData.getDeletions() != null) { for (CmsUUID id : clipboardData.getDeletions().keySet()) { if (id != null) { deleted.put(id.toString()); } } } user.setAdditionalInfo(ADDINFO_ADE_DELETED_LIST, deleted.toString()); cms.writeUser(user); } } /** * Determines if the title property of the default file should be changed.<p> * * @param properties the current default file properties * @param folderNavtext the 'NavText' property of the folder * * @return <code>true</code> if the title property should be changed */ private boolean shouldChangeDefaultFileTitle(Map<String, CmsProperty> properties, CmsProperty folderNavtext) { return (properties == null) || (properties.get(CmsPropertyDefinition.PROPERTY_TITLE) == null) || (properties.get(CmsPropertyDefinition.PROPERTY_TITLE).getValue() == null) || ((folderNavtext != null) && properties.get(CmsPropertyDefinition.PROPERTY_TITLE).getValue().equals( folderNavtext.getValue())); } /** * Determines if the title property should be changed in case of a 'NavText' change.<p> * * @param properties the current resource properties * * @return <code>true</code> if the title property should be changed in case of a 'NavText' change */ private boolean shouldChangeTitle(Map<String, CmsProperty> properties) { return (properties == null) || (properties.get(CmsPropertyDefinition.PROPERTY_TITLE) == null) || (properties.get(CmsPropertyDefinition.PROPERTY_TITLE).getValue() == null) || ((properties.get(CmsPropertyDefinition.PROPERTY_NAVTEXT) != null) && properties.get( CmsPropertyDefinition.PROPERTY_TITLE).getValue().equals( properties.get(CmsPropertyDefinition.PROPERTY_NAVTEXT).getValue())); } /** * Converts a jsp navigation element into a client sitemap entry.<p> * * @param navElement the jsp navigation element * @param isRoot true if the entry is a root entry * * @return the client sitemap entry * @throws CmsException */ private CmsClientSitemapEntry toClientEntry(CmsJspNavElement navElement, boolean isRoot) throws CmsException { CmsResource entryPage = null; CmsObject cms = getCmsObject(); CmsClientSitemapEntry clientEntry = new CmsClientSitemapEntry(); CmsResource entryFolder = null; CmsResource ownResource = navElement.getResource(); clientEntry.setResourceState(ownResource.getState()); CmsResource defaultFileResource = null; if (ownResource.isFolder()) { defaultFileResource = cms.readDefaultFile(ownResource, CmsResourceFilter.ONLY_VISIBLE); } Map<String, CmsClientProperty> ownProps = getClientProperties(cms, ownResource, false); Map<String, CmsClientProperty> defaultFileProps = null; if (defaultFileResource != null) { defaultFileProps = getClientProperties(cms, defaultFileResource, false); clientEntry.setDefaultFileId(defaultFileResource.getStructureId()); clientEntry.setDefaultFileType(OpenCms.getResourceManager().getResourceType(defaultFileResource.getTypeId()).getTypeName()); } else { defaultFileProps = new HashMap<String, CmsClientProperty>(); } boolean isDefault = isDefaultFile(ownResource); clientEntry.setId(ownResource.getStructureId()); clientEntry.setFolderDefaultPage(isDefault); if (navElement.getResource().isFolder()) { entryFolder = navElement.getResource(); entryPage = defaultFileResource; clientEntry.setName(entryFolder.getName()); if (entryPage == null) { entryPage = entryFolder; } if (!isRoot && isSubSitemap(navElement)) { clientEntry.setEntryType(EntryType.subSitemap); clientEntry.setDefaultFileType(null); } CmsLock folderLock = cms.getLock(entryFolder); clientEntry.setHasForeignFolderLock(!folderLock.isUnlocked() && !folderLock.isOwnedBy(cms.getRequestContext().getCurrentUser())); if (!cms.getRequestContext().getCurrentProject().isOnlineProject()) { List<CmsResource> blockingChildren = cms.getBlockingLockedResources(entryFolder); clientEntry.setBlockingLockedChildren((blockingChildren != null) && !blockingChildren.isEmpty()); } } else { entryPage = navElement.getResource(); clientEntry.setName(entryPage.getName()); if (isRedirectType(entryPage.getTypeId())) { clientEntry.setEntryType(EntryType.redirect); CmsFile file = cms.readFile(entryPage); I_CmsXmlDocument content = CmsXmlContentFactory.unmarshal(cms, file); Locale contentLocale = OpenCms.getLocaleManager().getDefaultLocale(cms, entryPage); // ensure the content contains the default locale contentLocale = content.getBestMatchingLocale(contentLocale); if (contentLocale == null) { // no best matching locale, use the first available List<Locale> locales = content.getLocales(); if (!locales.isEmpty()) { contentLocale = locales.get(0); } } String link = ""; if (contentLocale != null) { link = content.getValue(REDIRECT_LINK_TARGET_XPATH, contentLocale).getStringValue(getCmsObject()); } clientEntry.setRedirectTarget(link); } else { clientEntry.setEntryType(EntryType.leaf); } } long dateExpired = navElement.getResource().getDateExpired(); if (dateExpired != CmsResource.DATE_EXPIRED_DEFAULT) { clientEntry.setDateExpired(CmsDateUtil.getDate( new Date(dateExpired), DateFormat.SHORT, getWorkplaceLocale())); } long dateReleased = navElement.getResource().getDateReleased(); if (dateReleased != CmsResource.DATE_RELEASED_DEFAULT) { clientEntry.setDateReleased(CmsDateUtil.getDate( new Date(dateReleased), DateFormat.SHORT, getWorkplaceLocale())); } clientEntry.setResleasedAndNotExpired(navElement.getResource().isReleasedAndNotExpired( System.currentTimeMillis())); String path = cms.getSitePath(entryPage); clientEntry.setVfsPath(path); clientEntry.setOwnProperties(ownProps); clientEntry.setDefaultFileProperties(defaultFileProps); clientEntry.setSitePath(entryFolder != null ? cms.getSitePath(entryFolder) : path); //CHECK: assuming that, if entryPage refers to the default file, the lock state of the folder clientEntry.setLock(generateClientLock(entryPage)); clientEntry.setInNavigation(isRoot || navElement.isInNavigation()); String type = OpenCms.getResourceManager().getResourceType(ownResource).getTypeName(); clientEntry.setResourceTypeName(type); return clientEntry; } /** * Un-deletes a resource according to the change data.<p> * * @param change the change data * * @return the changed entry or <code>null</code> * * @throws CmsException if something goes wrong */ private CmsSitemapChange undelete(CmsSitemapChange change) throws CmsException { CmsObject cms = getCmsObject(); CmsResource deleted = cms.readResource(change.getEntryId(), CmsResourceFilter.ALL); if (deleted.getState().isDeleted()) { ensureLock(deleted); cms.undeleteResource(getCmsObject().getSitePath(deleted), true); tryUnlock(deleted); } String parentPath = CmsResource.getParentFolder(cms.getSitePath(deleted)); CmsJspNavElement navElement = getNavBuilder().getNavigationForResource( parentPath, CmsResourceFilter.ONLY_VISIBLE); CmsClientSitemapEntry entry = toClientEntry(navElement, navElement.isInNavigation()); entry.setSubEntries(getChildren(parentPath, 2, null), null); change.setUpdatedEntry(entry); return change; } /** * Updates the navigation position for a resource.<p> * * @param res the resource for which to update the navigation position * @param change the sitemap change * * @throws CmsException if something goes wrong */ private void updateNavPos(CmsResource res, CmsSitemapChange change) throws CmsException { if (change.hasChangedPosition()) { applyNavigationChanges(change, res); } } /** * Updates properties for a resource and possibly its detail page.<p> * * @param cms the CMS context * @param ownRes the resource * @param defaultFileRes the default file resource (possibly null) * @param propertyModifications the property modifications * * @throws CmsException if something goes wrong */ private void updateProperties( CmsObject cms, CmsResource ownRes, CmsResource defaultFileRes, List<CmsPropertyModification> propertyModifications) throws CmsException { Map<String, CmsProperty> ownProps = getPropertiesByName(cms.readPropertyObjects(ownRes, false)); // determine if the title property should be changed in case of a 'NavText' change boolean changeOwnTitle = shouldChangeTitle(ownProps); boolean changeDefaultFileTitle = false; Map<String, CmsProperty> defaultFileProps = Collections.emptyMap(); if (defaultFileRes != null) { defaultFileProps = getPropertiesByName(cms.readPropertyObjects(defaultFileRes, false)); // determine if the title property of the default file should be changed changeDefaultFileTitle = shouldChangeDefaultFileTitle( defaultFileProps, ownProps.get(CmsPropertyDefinition.PROPERTY_NAVTEXT)); } String hasNavTextChange = null; List<CmsProperty> ownPropertyChanges = new ArrayList<CmsProperty>(); List<CmsProperty> defaultFilePropertyChanges = new ArrayList<CmsProperty>(); for (CmsPropertyModification propMod : propertyModifications) { CmsProperty propToModify = null; if (ownRes.getStructureId().equals(propMod.getId())) { if (CmsPropertyDefinition.PROPERTY_NAVTEXT.equals(propMod.getName())) { hasNavTextChange = propMod.getValue(); } else if (CmsPropertyDefinition.PROPERTY_TITLE.equals(propMod.getName())) { changeOwnTitle = false; } propToModify = ownProps.get(propMod.getName()); if (propToModify == null) { propToModify = new CmsProperty(propMod.getName(), null, null); } ownPropertyChanges.add(propToModify); } else { if (CmsPropertyDefinition.PROPERTY_TITLE.equals(propMod.getName())) { changeDefaultFileTitle = false; } propToModify = defaultFileProps.get(propMod.getName()); if (propToModify == null) { propToModify = new CmsProperty(propMod.getName(), null, null); } defaultFilePropertyChanges.add(propToModify); } String newValue = propMod.getValue(); if (newValue == null) { newValue = ""; } if (propMod.isStructureValue()) { propToModify.setStructureValue(newValue); } else { propToModify.setResourceValue(newValue); } } if (hasNavTextChange != null) { if (changeOwnTitle) { CmsProperty titleProp = ownProps.get(CmsPropertyDefinition.PROPERTY_TITLE); if (titleProp == null) { titleProp = new CmsProperty(CmsPropertyDefinition.PROPERTY_TITLE, null, null); } titleProp.setStructureValue(hasNavTextChange); ownPropertyChanges.add(titleProp); } if (changeDefaultFileTitle) { CmsProperty titleProp = defaultFileProps.get(CmsPropertyDefinition.PROPERTY_TITLE); if (titleProp == null) { titleProp = new CmsProperty(CmsPropertyDefinition.PROPERTY_TITLE, null, null); } titleProp.setStructureValue(hasNavTextChange); defaultFilePropertyChanges.add(titleProp); } } if (!ownPropertyChanges.isEmpty()) { cms.writePropertyObjects(ownRes, ownPropertyChanges); } if (!defaultFilePropertyChanges.isEmpty() && (defaultFileRes != null)) { cms.writePropertyObjects(defaultFileRes, defaultFilePropertyChanges); } } }