View Javadoc

1   /**
2    * This file Copyright (c) 2012 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.googlesitemap.service;
35  
36  import info.magnolia.cms.i18n.I18nContentSupport;
37  import info.magnolia.context.MgnlContext;
38  import info.magnolia.jcr.predicate.AbstractPredicate;
39  import info.magnolia.jcr.util.NodeTypes;
40  import info.magnolia.jcr.util.NodeUtil;
41  import info.magnolia.jcr.util.SessionUtil;
42  import info.magnolia.jcr.wrapper.JCRMgnlPropertiesFilteringNodeWrapper;
43  import info.magnolia.link.LinkUtil;
44  import info.magnolia.module.googlesitemap.GoogleSiteMapConfiguration;
45  import info.magnolia.module.googlesitemap.bean.SiteMapEntry;
46  import info.magnolia.module.googlesitemap.service.query.QueryUtil;
47  import info.magnolia.module.templatingkit.sites.Site;
48  import info.magnolia.module.templatingkit.sites.SiteManager;
49  import info.magnolia.objectfactory.Components;
50  import info.magnolia.repository.RepositoryConstants;
51  
52  import java.util.ArrayList;
53  import java.util.Calendar;
54  import java.util.HashSet;
55  import java.util.Iterator;
56  import java.util.List;
57  import java.util.Locale;
58  import java.util.Set;
59  
60  import javax.inject.Inject;
61  import javax.inject.Singleton;
62  import javax.jcr.Node;
63  import javax.jcr.NodeIterator;
64  import javax.jcr.Property;
65  import javax.jcr.PropertyIterator;
66  import javax.jcr.RepositoryException;
67  
68  import org.apache.commons.lang.StringUtils;
69  import org.apache.jackrabbit.commons.iterator.FilteringNodeIterator;
70  import org.apache.jackrabbit.commons.predicate.NodeTypePredicate;
71  import org.apache.jackrabbit.commons.predicate.Predicate;
72  import org.slf4j.Logger;
73  import org.slf4j.LoggerFactory;
74  
75  
76  /**
77   * Main SiteMapService.
78   * This service is responsible for:
79   *   Searching the appropriate nodes (pages or VirtualUri) that should be displayed (for XML rendering or Editing).
80   *   From the search nodes, create SiteMapEntrys (POJO containing preformated infos used for the rendering).
81   *
82   * @version $Id$
83   *
84   */
85  @Singleton
86  public class SiteMapService {
87  
88      private static final Logger log = LoggerFactory.getLogger(SiteMapService.class);
89  
90      // Used to filter the current node and just get child nodes starting with SITE_DIALOG_CONFIGURATION_NAME
91      private static Predicate All_Site_Nodes = new AbstractPredicate<Node>() {
92          @Override
93          public boolean evaluateTyped(Node node) {
94              try {
95                  return node.getName().startsWith(GoogleSiteMapConfiguration.SITE_DIALOG_CONFIGURATION_NAME);
96              } catch (RepositoryException e) {
97                  return false;
98              }
99          }
100     };
101 
102     /**
103      * Injected service.
104      */
105     private SiteManager siteManager;
106     private I18nContentSupport i18nSupport;
107     private GoogleSiteMapConfiguration configuration;
108     private QueryUtil queryUtil;
109 
110 
111     /**
112      * Constructor for injection.
113      * @param siteManager: Injected.
114      */
115     @Inject
116     public SiteMapService(SiteManager siteManager, GoogleSiteMapConfiguration configuration, QueryUtil queryUtil) {
117         this.siteManager = siteManager;
118         this.configuration = configuration;
119         this.queryUtil = queryUtil;
120         i18nSupport = Components.getComponent(I18nContentSupport.class);
121     }
122 
123 
124     /**
125      * Create the SiteMapEntry List corresponding to the uriMapping.
126      */
127     public List<SiteMapEntry> getSiteMapBeanForVirtualUri( boolean isForEdit) throws RepositoryException{
128         // Init
129         List<SiteMapEntry> res = new ArrayList<SiteMapEntry>();
130         NodeIterator nodeIterator = null;
131 
132         log.info("Requested virtualUri info's for EDIT ");
133         // Create a virtualUri info beans
134         nodeIterator = searchVirtualUriNodes();
135         feedVirtualUriMapBeans(res, nodeIterator, isForEdit);
136 
137         return res;
138     }
139 
140     /**
141      * Create the SiteMapEntry for all child's of the rootNode that are of type MgnlNodeType.NT_CONTENT.
142      */
143     public List<SiteMapEntry> getSiteMapBeanForSite(Node rootNode, boolean isForEdit) throws RepositoryException{
144         // Init
145         List<SiteMapEntry> res = new ArrayList<SiteMapEntry>();
146         NodeIterator nodeIterator = null;
147 
148         log.info("Requested siteMap info's for EDIT based on the following root: "+rootNode.getPath());
149         // Create a site info beans
150         nodeIterator = searchSiteChildNodes(rootNode);
151         feedSiteMapBeans(res, nodeIterator, isForEdit, null);
152 
153         return res;
154     }
155 
156     /**
157      * Search the nodes related to the UriMappings.
158      */
159     private NodeIterator searchVirtualUriNodes() {
160         NodeIterator nodes = null;
161 
162         String xpath = "//virtualURIMapping//element(*," + NodeTypes.ContentNode.NAME + ")";
163         nodes = queryUtil.query(RepositoryConstants.CONFIG, xpath, "xpath", NodeTypes.ContentNode.NAME);
164 
165         return nodes;
166     }
167 
168 
169     /**
170      * Extract informations form the Node and create a Bean used in the display
171      * for Edit and XML rendering.
172      */
173     private void feedVirtualUriMapBeans(List<SiteMapEntry> siteMapBeans, NodeIterator nodes, boolean isForEdit) throws RepositoryException {
174         while(nodes.hasNext()) {
175             // Init
176             Node child = nodes.nextNode();
177             if(hasToBePopulated(child, true, isForEdit, null)) {
178                 //Populate the Bean used by the display
179                 SiteMapEntry siteMapBean = populateBean(child, true, isForEdit , null, null);
180                 //Add the bean to the result
181                 siteMapBeans.add(siteMapBean);
182             }
183         }
184     }
185 
186 
187     /**
188      * Search children of the root Node of type MgnlNodeType.NT_CONTENT and all inherited node types.
189      * @throws RepositoryException
190      */
191     private NodeIterator searchSiteChildNodes(Node root) throws RepositoryException {
192         NodeIterator nodes = null;
193 
194         NodeTypePredicate predicate = new NodeTypePredicate(NodeTypes.Content.NAME, true);
195         nodes = new FilteringNodeIterator(root.getNodes(), predicate);
196 
197         return nodes;
198     }
199 
200 
201     /**
202      * Create a new SiteMapEntry POJO for all node from the NodeIterator if required.
203      */
204     private void feedSiteMapBeans(List<SiteMapEntry> siteMapBeans, NodeIterator nodes,  boolean isForEdit, Boolean inheritedParentDisplayColor) throws RepositoryException {
205 
206         while(nodes.hasNext()) {
207             // Init
208             Node child = nodes.nextNode();
209             Site site = siteManager.getAssignedSite(child);
210 
211             if(hasToBePopulated(child, false, isForEdit, site)) {
212                 //Populate the Bean used by the display
213                 SiteMapEntry siteMapBean = populateBean(child, false, isForEdit , site, inheritedParentDisplayColor);
214                 NodeIterator childNodes = searchSiteChildNodes(child);
215                 boolean hasChild = (childNodes!=null && childNodes.hasNext()) ? true:false;
216 
217                 if(isForEdit) {
218                     // Handle Edit Mode. Always all the bean.
219                     siteMapBeans.add(siteMapBean);
220 
221                     if(hasChild) {
222                         // Handle the children if any
223                         Boolean hideNodeChildInGoogleSitemapTmp = null;
224                         // Handle the color of the child to be display.
225                         if(inheritedParentDisplayColor != null) {
226                             hideNodeChildInGoogleSitemapTmp = inheritedParentDisplayColor;
227                         }else if(hideNodeChildInGoogleSitemap(child)) {
228                             hideNodeChildInGoogleSitemapTmp = Boolean.TRUE;
229                         }
230 
231                         feedSiteMapBeans(siteMapBeans, childNodes, isForEdit, hideNodeChildInGoogleSitemapTmp);
232                     }
233 
234                 } else {
235                     // Handle XML Display Mode
236                     // If node has to be display.
237                     if (!hideNodeInGoogleSitemap(child)){
238                         // Check Multilang.
239                         if(i18nSupport.isEnabled() && site.getI18n().isEnabled()) {
240                             Locale currentLocale = i18nSupport.getLocale();
241                             for(Locale locale:site.getI18n().getLocales() ){
242                                 i18nSupport.setLocale(locale);
243                                 SiteMapEntry siteMapBeanLocale = populateBean(child, false, isForEdit, site, null);
244                                 if(siteMapBeanLocale != null) {
245                                     siteMapBeans.add(siteMapBeanLocale);
246                                 }
247                             }
248                             i18nSupport.setLocale(currentLocale);
249                         } else {
250                             siteMapBeans.add(siteMapBean);
251                         }
252                     }
253                     // Check if Child Node has to be included
254                     if(hasChild && !hideNodeChildInGoogleSitemap(child)) {
255                         // Call recursively
256                         feedSiteMapBeans(siteMapBeans, childNodes, isForEdit, null);
257                     }
258 
259                 }
260 
261             }
262 
263         }
264     }
265 
266     /**
267      * Populate the Bean.
268      */
269     private SiteMapEntry populateBean( Node child, boolean isForUriMapping, boolean isForEdit, Site site, Boolean inheritedParentDisplayColor ) throws RepositoryException {
270         SiteMapEntry res = null;
271         // Populate only if true
272         if(hasToBePopulated(child, isForUriMapping, isForEdit, site)) {
273             double priority = 0.5;
274             String changefreq = "weekly";
275             String lastmod = "";
276             int level = -1;
277             String loc = null;
278 
279             // Get Level
280             level = child.getDepth();
281 
282             // Get priority
283             if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_PRIORITY_NODEDATA)) {
284                 priority = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_PRIORITY_NODEDATA).getDouble();
285             }
286 
287             // Get changefreq
288             if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_CHANGEFREQ_NODEDATA)) {
289                 changefreq = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_CHANGEFREQ_NODEDATA).getString();
290             }
291 
292             // Get lastmod
293             Calendar date = null;
294             if (NodeTypes.LastModified.getLastModified(child) != null) {
295                 date = NodeTypes.LastModified.getLastModified(child);
296             } else {
297                 date = NodeTypes.Created.getCreated(child);
298             }
299             lastmod = configuration.getFastDateFormat().format(date.getTime());
300 
301             // Get loc
302             if(isForUriMapping) {
303                 loc = child.hasProperty("fromURI")? MgnlContext.getContextPath()+child.getProperty("fromURI").getString() : "";
304             }else {
305                 loc = LinkUtil.createExternalLink(child);
306             }
307 
308             //Populate the bean:
309             res = new SiteMapEntry(loc, lastmod, changefreq, Double.toString(priority), level);
310             log.debug("Populate Basic info for Node: "+child.getPath()+ " with values "+res.toStringDisplay());
311 
312             if(isForEdit) {
313                 res = populateBeanForEdit(child, isForUriMapping, res, inheritedParentDisplayColor);
314             }
315         }
316 
317         return res;
318     }
319 
320 
321     /**
322      * Add additional info's for Edit.
323      */
324     private SiteMapEntry populateBeanForEdit(Node child, boolean isForUriMapping, SiteMapEntry siteMapBean, Boolean inheritedParentDisplayColor) throws RepositoryException {
325         //Common fields
326         siteMapBean.setPath(child.getPath());
327 
328         if(isForUriMapping) {
329             //For VirtualUri
330             siteMapBean.setFrom(child.hasProperty("fromURI")?child.getProperty("fromURI").getString():"");
331             siteMapBean.setTo(child.hasProperty("toURI")?child.getProperty("toURI").getString():"");
332             siteMapBean.setStyleAlert(hideNodeInGoogleSitemap(child));
333             log.debug("Populate Edit VirtualUri info for Node: "+child.getPath()+ " with values "+siteMapBean.toStringVirtualUri());
334         } else {
335             //For Site
336             siteMapBean.setPageName(child.hasProperty("title")?child.getProperty("title").getString():"");
337             siteMapBean.setStyleAlert(inheritedParentDisplayColor!=null?inheritedParentDisplayColor.booleanValue():hideNodeInGoogleSitemap(child));
338             log.debug("Populate Edit Site info for Node: "+child.getPath()+ " with values "+siteMapBean.toStringSite());
339         }
340         return siteMapBean;
341     }
342 
343 
344     /**
345      * Check if the node has to be populated.
346      * True for UriMapping if:
347      *  !hideInGoogleSitemap && node has a modification date
348      * True for Site if:
349      *  !hideInGoogleSitemap && !hideInGoogleSitemapChildren &&  node has a creation date && site.isEnabled()
350      *
351      */
352     private boolean hasToBePopulated( Node child, boolean isForUriMapping, boolean isForEdit, Site site ) throws RepositoryException {
353         boolean hasCreationDate = NodeTypes.Created.getCreated(child) != null;
354         boolean hasModificationDate = NodeTypes.LastModified.getLastModified(child) != null;
355         boolean hasTemplate = StringUtils.isNotBlank(NodeTypes.Renderable.getTemplate(child));
356 
357         // Exclude SiteMap pages
358         if (hasTemplate && NodeTypes.Renderable.getTemplate(child).endsWith(GoogleSiteMapConfiguration.SITE_MAP_TEMPLATE_NAME)) {
359             return false;
360         }
361 
362         // For edit and if has creation date --> true.
363         if(isForEdit) {
364             return hasCreationDate;
365         }
366 
367         // Node has a property set to hide node to be display --> false.
368         if(hideNodeInGoogleSitemap(child) && (isForUriMapping || hideNodeChildInGoogleSitemap(child))) {
369             return false;
370         }
371         // For uriMapping.
372         if(isForUriMapping  && hasModificationDate) {
373             return true;
374         }
375         // For site.
376         if(!isForUriMapping && site.isEnabled() && hasCreationDate ) {
377             return true;
378         }
379 
380         return false;
381     }
382 
383     /**
384      * Check if we have to display in SiteMap.
385      */
386     private boolean hideNodeInGoogleSitemap(Node child) throws RepositoryException {
387         boolean res = false;
388 
389         if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAP_NODEDATA)) {
390             res = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAP_NODEDATA).getBoolean();
391         }
392 
393         return res;
394     }
395 
396     /**
397      * Check if we have to display in SiteMap.
398      */
399     private boolean hideNodeChildInGoogleSitemap(Node child) throws RepositoryException {
400         boolean res = false;
401 
402         if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAPCHILDREN_NODEDATA)) {
403             res = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAPCHILDREN_NODEDATA).getBoolean();
404         }
405 
406         return res;
407     }
408 
409 
410     /**
411      * Get Site and virtualUri informations if they are defined as components.
412      * Notice that by using a HashSet, a single object will be keeped in case of equality of the location.
413      */
414     public Iterator<SiteMapEntry> getSiteMapBeans(Node content) throws RepositoryException {
415         Set<SiteMapEntry> res = new HashSet<SiteMapEntry>();
416         // Return if no content set.
417         if (!NodeUtil.isNodeType(content, GoogleSiteMapConfiguration.NODE_TYPE)) {
418             return res.iterator();
419         }
420 
421         // Display Site informations part... if defined
422         Node node = null;
423         Iterator<Node> iterator = NodeUtil.collectAllChildren(content, All_Site_Nodes).iterator();
424         while (iterator.hasNext()) {
425             // Get node
426             node = iterator.next();
427 
428             Node childNode = new JCRMgnlPropertiesFilteringNodeWrapper(node);
429             PropertyIterator properties = childNode.getProperties();
430 
431             while (properties.hasNext()) {
432                 Property property = properties.nextProperty();
433                 Node root = SessionUtil.getNode(RepositoryConstants.WEBSITE, property.getString());
434                 // Call service and Remove Duplicate
435                 res.addAll(getSiteMapBeanForSite(root, false));
436 
437             }
438         }
439 
440         boolean includeVirtualUris =
441             content.hasProperty(GoogleSiteMapConfiguration.INCLUDE_VIRTUAL_URI_MAPPINGS_PROPERTIES) &&
442             content.getProperty(GoogleSiteMapConfiguration.INCLUDE_VIRTUAL_URI_MAPPINGS_PROPERTIES).getBoolean();
443         if (includeVirtualUris) {
444             res.addAll(getSiteMapBeanForVirtualUri(false));
445         }
446         return res.iterator();
447     }
448 
449 }