/** * This file Copyright (c) 2012 Magnolia International * Ltd. (http://www.magnolia-cms.com). All rights reserved. * * * This file is dual-licensed under both the Magnolia * Network Agreement and the GNU General Public License. * You may elect to use one or the other of these licenses. * * This file is distributed in the hope that it will be * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. * Redistribution, except as permitted by whichever of the GPL * or MNA you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or * modify this file under the terms of the GNU General * Public License, Version 3, as published by the Free Software * Foundation. You should have received a copy of the GNU * General Public License, Version 3 along with this program; * if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * 2. For the Magnolia Network Agreement (MNA), this file * and the accompanying materials are made available under the * terms of the MNA which accompanies this distribution, and * is available at http://www.magnolia-cms.com/mna.html * * Any modifications to this file must keep this entire header * intact. * */ package info.magnolia.module.googlesitemap.service; import info.magnolia.cms.i18n.I18nContentSupport; import info.magnolia.context.MgnlContext; import info.magnolia.jcr.predicate.AbstractPredicate; import info.magnolia.jcr.util.NodeTypes; import info.magnolia.jcr.util.NodeUtil; import info.magnolia.jcr.util.SessionUtil; import info.magnolia.jcr.wrapper.JCRMgnlPropertiesFilteringNodeWrapper; import info.magnolia.link.LinkUtil; import info.magnolia.module.googlesitemap.GoogleSiteMapConfiguration; import info.magnolia.module.googlesitemap.bean.SiteMapEntry; import info.magnolia.module.googlesitemap.service.query.QueryUtil; import info.magnolia.module.templatingkit.sites.Site; import info.magnolia.module.templatingkit.sites.SiteManager; import info.magnolia.objectfactory.Components; import info.magnolia.repository.RepositoryConstants; import java.util.ArrayList; import java.util.Calendar; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import org.apache.commons.lang.StringUtils; import org.apache.jackrabbit.commons.iterator.FilteringNodeIterator; import org.apache.jackrabbit.commons.predicate.NodeTypePredicate; import org.apache.jackrabbit.commons.predicate.Predicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Main SiteMapService. * This service is responsible for: * Searching the appropriate nodes (pages or VirtualUri) that should be displayed (for XML rendering or Editing). * From the search nodes, create SiteMapEntrys (POJO containing preformated infos used for the rendering). * * @version $Id$ * */ @Singleton public class SiteMapService { private static final Logger log = LoggerFactory.getLogger(SiteMapService.class); public static final double DEFAULT_PRIORITY = 0.5; public static final String DEFAULT_CHANGE_FREQUENCY = "weekly"; // Used to filter the current node and just get child nodes starting with SITE_DIALOG_CONFIGURATION_NAME private static Predicate All_Site_Nodes = new AbstractPredicate() { @Override public boolean evaluateTyped(Node node) { try { return node.getName().startsWith(GoogleSiteMapConfiguration.SITE_DIALOG_CONFIGURATION_NAME); } catch (RepositoryException e) { return false; } } }; /** * Injected service. */ private SiteManager siteManager; private I18nContentSupport i18nSupport; private GoogleSiteMapConfiguration configuration; private QueryUtil queryUtil; /** * Constructor for injection. * @param siteManager: Injected. */ @Inject public SiteMapService(SiteManager siteManager, GoogleSiteMapConfiguration configuration, QueryUtil queryUtil) { this.siteManager = siteManager; this.configuration = configuration; this.queryUtil = queryUtil; i18nSupport = Components.getComponent(I18nContentSupport.class); } /** * Create the SiteMapEntry List corresponding to the uriMapping. */ public List getSiteMapBeanForVirtualUri( boolean isForEdit) throws RepositoryException{ // Init List res = new ArrayList(); NodeIterator nodeIterator = null; log.info("Requested virtualUri info's for EDIT "); // Create a virtualUri info beans nodeIterator = searchVirtualUriNodes(); feedVirtualUriMapBeans(res, nodeIterator, isForEdit); return res; } /** * Create the SiteMapEntry for all child's of the rootNode that are of type MgnlNodeType.NT_CONTENT. */ public List getSiteMapBeanForSite(Node rootNode, boolean isForEdit) throws RepositoryException{ // Init List res = new ArrayList(); NodeIterator nodeIterator = null; log.info("Requested siteMap info's for EDIT based on the following root: "+rootNode.getPath()); // Create a site info beans nodeIterator = searchSiteChildNodes(rootNode); feedSiteMapBeans(res, nodeIterator, isForEdit, null); return res; } /** * Search the nodes related to the UriMappings. */ private NodeIterator searchVirtualUriNodes() { NodeIterator nodes = null; String xpath = "//virtualURIMapping//element(*," + NodeTypes.ContentNode.NAME + ")"; nodes = queryUtil.query(RepositoryConstants.CONFIG, xpath, "xpath", NodeTypes.ContentNode.NAME); return nodes; } /** * Extract informations form the Node and create a Bean used in the display * for Edit and XML rendering. */ private void feedVirtualUriMapBeans(List siteMapBeans, NodeIterator nodes, boolean isForEdit) throws RepositoryException { while(nodes.hasNext()) { // Init Node child = nodes.nextNode(); if(hasToBePopulated(child, true, isForEdit, null)) { //Populate the Bean used by the display SiteMapEntry siteMapBean = populateBean(child, true, isForEdit , null, null); //Add the bean to the result siteMapBeans.add(siteMapBean); } } } /** * Search children of the root Node of type MgnlNodeType.NT_CONTENT and all inherited node types. * @throws RepositoryException */ private NodeIterator searchSiteChildNodes(Node root) throws RepositoryException { NodeIterator nodes = null; NodeTypePredicate predicate = new NodeTypePredicate(NodeTypes.Content.NAME, true); nodes = new FilteringNodeIterator(root.getNodes(), predicate); return nodes; } /** * Create a new SiteMapEntry POJO for all node from the NodeIterator if required. */ private void feedSiteMapBeans(List siteMapBeans, NodeIterator nodes, boolean isForEdit, Boolean inheritedParentDisplayColor) throws RepositoryException { while(nodes.hasNext()) { // Init Node child = nodes.nextNode(); Site site = siteManager.getAssignedSite(child); if(hasToBePopulated(child, false, isForEdit, site)) { //Populate the Bean used by the display SiteMapEntry siteMapBean = populateBean(child, false, isForEdit , site, inheritedParentDisplayColor); NodeIterator childNodes = searchSiteChildNodes(child); boolean hasChild = (childNodes!=null && childNodes.hasNext()) ? true:false; if(isForEdit) { // Handle Edit Mode. Always all the bean. siteMapBeans.add(siteMapBean); if(hasChild) { // Handle the children if any Boolean hideNodeChildInGoogleSitemapTmp = null; // Handle the color of the child to be display. if(inheritedParentDisplayColor != null) { hideNodeChildInGoogleSitemapTmp = inheritedParentDisplayColor; }else if(hideNodeChildInGoogleSitemap(child)) { hideNodeChildInGoogleSitemapTmp = Boolean.TRUE; } feedSiteMapBeans(siteMapBeans, childNodes, isForEdit, hideNodeChildInGoogleSitemapTmp); } } else { // Handle XML Display Mode // If node has to be display. if (!hideNodeInGoogleSitemap(child)){ try { // Check Multilang. if(i18nSupport.isEnabled() && site.getI18n().isEnabled()) { Locale currentLocale = i18nSupport.getLocale(); for(Locale locale:site.getI18n().getLocales() ){ i18nSupport.setLocale(locale); SiteMapEntry siteMapBeanLocale = populateBean(child, false, isForEdit, site, null); if(siteMapBeanLocale != null) { siteMapBeans.add(siteMapBeanLocale); } } i18nSupport.setLocale(currentLocale); } else { siteMapBeans.add(siteMapBean); } } catch (Exception e) { siteMapBeans.add(siteMapBean); } } // Check if Child Node has to be included if(hasChild && !hideNodeChildInGoogleSitemap(child)) { // Call recursively feedSiteMapBeans(siteMapBeans, childNodes, isForEdit, null); } } } } } /** * Populate the Bean. */ private SiteMapEntry populateBean( Node child, boolean isForUriMapping, boolean isForEdit, Site site, Boolean inheritedParentDisplayColor ) throws RepositoryException { SiteMapEntry res = null; // Populate only if true if(hasToBePopulated(child, isForUriMapping, isForEdit, site)) { String lastmod = ""; int level = -1; String loc = null; double priority = DEFAULT_PRIORITY; String changefreq = DEFAULT_CHANGE_FREQUENCY; // Get Level level = child.getDepth(); // Get priority if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_PRIORITY_NODEDATA)) { priority = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_PRIORITY_NODEDATA).getDouble(); } // Get changefreq if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_CHANGEFREQ_NODEDATA)) { changefreq = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_CHANGEFREQ_NODEDATA).getString(); } // Get lastmod Calendar date = null; if (NodeTypes.LastModified.getLastModified(child) != null) { date = NodeTypes.LastModified.getLastModified(child); } else { date = NodeTypes.Created.getCreated(child); } lastmod = configuration.getFastDateFormat().format(date.getTime()); // Get loc if(isForUriMapping) { loc = child.hasProperty("fromURI")? MgnlContext.getContextPath()+child.getProperty("fromURI").getString() : ""; }else { loc = LinkUtil.createExternalLink(child); } //Populate the bean: res = new SiteMapEntry(loc, lastmod, changefreq, Double.toString(priority), level); log.debug("Populate Basic info for Node: "+child.getPath()+ " with values "+res.toStringDisplay()); if(isForEdit) { res = populateBeanForEdit(child, isForUriMapping, res, inheritedParentDisplayColor); } } return res; } /** * Add additional info's for Edit. */ private SiteMapEntry populateBeanForEdit(Node child, boolean isForUriMapping, SiteMapEntry siteMapBean, Boolean inheritedParentDisplayColor) throws RepositoryException { //Common fields siteMapBean.setPath(child.getPath()); if(isForUriMapping) { //For VirtualUri siteMapBean.setFrom(child.hasProperty("fromURI")?child.getProperty("fromURI").getString():""); siteMapBean.setTo(child.hasProperty("toURI")?child.getProperty("toURI").getString():""); siteMapBean.setStyleAlert(hideNodeInGoogleSitemap(child)); log.debug("Populate Edit VirtualUri info for Node: "+child.getPath()+ " with values "+siteMapBean.toStringVirtualUri()); } else { //For Site siteMapBean.setPageName(child.hasProperty("title")?child.getProperty("title").getString():""); siteMapBean.setStyleAlert(inheritedParentDisplayColor!=null?inheritedParentDisplayColor.booleanValue():hideNodeInGoogleSitemap(child)); log.debug("Populate Edit Site info for Node: "+child.getPath()+ " with values "+siteMapBean.toStringSite()); } return siteMapBean; } /** * Check if the node has to be populated. * True for UriMapping if: * !hideInGoogleSitemap && node has a modification date * True for Site if: * !hideInGoogleSitemap && !hideInGoogleSitemapChildren && node has a creation date && site.isEnabled() * */ private boolean hasToBePopulated( Node child, boolean isForUriMapping, boolean isForEdit, Site site ) throws RepositoryException { boolean hasCreationDate = NodeTypes.Created.getCreated(child) != null; boolean hasModificationDate = NodeTypes.LastModified.getLastModified(child) != null; boolean hasTemplate = StringUtils.isNotBlank(NodeTypes.Renderable.getTemplate(child)); // Exclude SiteMap pages if (hasTemplate && NodeTypes.Renderable.getTemplate(child).endsWith(GoogleSiteMapConfiguration.SITE_MAP_TEMPLATE_NAME)) { return false; } // For edit and if has creation date --> true. if(isForEdit) { return hasCreationDate; } // Node has a property set to hide node to be display --> false. if(hideNodeInGoogleSitemap(child) && (isForUriMapping || hideNodeChildInGoogleSitemap(child))) { return false; } // For uriMapping. if(isForUriMapping && hasModificationDate) { return true; } // For site. if(!isForUriMapping && site.isEnabled() && hasCreationDate ) { return true; } return false; } /** * Check if we have to display in SiteMap. */ private boolean hideNodeInGoogleSitemap(Node child) throws RepositoryException { boolean res = false; if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAP_NODEDATA)) { res = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAP_NODEDATA).getBoolean(); } return res; } /** * Check if we have to display in SiteMap. */ private boolean hideNodeChildInGoogleSitemap(Node child) throws RepositoryException { boolean res = false; if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAPCHILDREN_NODEDATA)) { res = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAPCHILDREN_NODEDATA).getBoolean(); } return res; } /** * Get Site and virtualUri informations if they are defined as components. * Notice that by using a HashSet, a single object will be keeped in case of equality of the location. */ public Iterator getSiteMapBeans(Node content) throws RepositoryException { Set res = new HashSet(); // Return if no content set. if (!NodeUtil.isNodeType(content, GoogleSiteMapConfiguration.NODE_TYPE)) { return res.iterator(); } // Display Site informations part... if defined Node node = null; Iterator iterator = NodeUtil.collectAllChildren(content, All_Site_Nodes).iterator(); while (iterator.hasNext()) { // Get node node = iterator.next(); Node childNode = new JCRMgnlPropertiesFilteringNodeWrapper(node); PropertyIterator properties = childNode.getProperties(); while (properties.hasNext()) { Property property = properties.nextProperty(); Node root = SessionUtil.getNode(RepositoryConstants.WEBSITE, property.getString()); // Call service and Remove Duplicate res.addAll(getSiteMapBeanForSite(root, false)); } } boolean includeVirtualUris = content.hasProperty(GoogleSiteMapConfiguration.INCLUDE_VIRTUAL_URI_MAPPINGS_PROPERTIES) && content.getProperty(GoogleSiteMapConfiguration.INCLUDE_VIRTUAL_URI_MAPPINGS_PROPERTIES).getBoolean(); if (includeVirtualUris) { res.addAll(getSiteMapBeanForVirtualUri(false)); } return res.iterator(); } }