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      public static final double DEFAULT_PRIORITY = 0.5;
91      public static final String DEFAULT_CHANGE_FREQUENCY = "weekly";
92  
93      // Used to filter the current node and just get child nodes starting with SITE_DIALOG_CONFIGURATION_NAME
94      private static Predicate All_Site_Nodes = new AbstractPredicate<Node>() {
95          @Override
96          public boolean evaluateTyped(Node node) {
97              try {
98                  return node.getName().startsWith(GoogleSiteMapConfiguration.SITE_DIALOG_CONFIGURATION_NAME);
99              } catch (RepositoryException e) {
100                 return false;
101             }
102         }
103     };
104 
105     /**
106      * Injected service.
107      */
108     private SiteManager siteManager;
109     private I18nContentSupport i18nSupport;
110     private GoogleSiteMapConfiguration configuration;
111     private QueryUtil queryUtil;
112 
113 
114     /**
115      * Constructor for injection.
116      * @param siteManager: Injected.
117      */
118     @Inject
119     public SiteMapService(SiteManager siteManager, GoogleSiteMapConfiguration configuration, QueryUtil queryUtil) {
120         this.siteManager = siteManager;
121         this.configuration = configuration;
122         this.queryUtil = queryUtil;
123         i18nSupport = Components.getComponent(I18nContentSupport.class);
124     }
125 
126 
127     /**
128      * Create the SiteMapEntry List corresponding to the uriMapping.
129      */
130     public List<SiteMapEntry> getSiteMapBeanForVirtualUri( boolean isForEdit) throws RepositoryException{
131         // Init
132         List<SiteMapEntry> res = new ArrayList<SiteMapEntry>();
133         NodeIterator nodeIterator = null;
134 
135         log.info("Requested virtualUri info's for EDIT ");
136         // Create a virtualUri info beans
137         nodeIterator = searchVirtualUriNodes();
138         feedVirtualUriMapBeans(res, nodeIterator, isForEdit);
139 
140         return res;
141     }
142 
143     /**
144      * Create the SiteMapEntry for all child's of the rootNode that are of type MgnlNodeType.NT_CONTENT.
145      */
146     public List<SiteMapEntry> getSiteMapBeanForSite(Node rootNode, boolean isForEdit) throws RepositoryException{
147         // Init
148         List<SiteMapEntry> res = new ArrayList<SiteMapEntry>();
149         NodeIterator nodeIterator = null;
150 
151         log.info("Requested siteMap info's for EDIT based on the following root: "+rootNode.getPath());
152         // Create a site info beans
153         nodeIterator = searchSiteChildNodes(rootNode);
154         feedSiteMapBeans(res, nodeIterator, isForEdit, null);
155 
156         return res;
157     }
158 
159     /**
160      * Search the nodes related to the UriMappings.
161      */
162     private NodeIterator searchVirtualUriNodes() {
163         NodeIterator nodes = null;
164 
165         String xpath = "//virtualURIMapping//element(*," + NodeTypes.ContentNode.NAME + ")";
166         nodes = queryUtil.query(RepositoryConstants.CONFIG, xpath, "xpath", NodeTypes.ContentNode.NAME);
167 
168         return nodes;
169     }
170 
171 
172     /**
173      * Extract informations form the Node and create a Bean used in the display
174      * for Edit and XML rendering.
175      */
176     private void feedVirtualUriMapBeans(List<SiteMapEntry> siteMapBeans, NodeIterator nodes, boolean isForEdit) throws RepositoryException {
177         while(nodes.hasNext()) {
178             // Init
179             Node child = nodes.nextNode();
180             if(hasToBePopulated(child, true, isForEdit, null)) {
181                 //Populate the Bean used by the display
182                 SiteMapEntry siteMapBean = populateBean(child, true, isForEdit , null, null);
183                 //Add the bean to the result
184                 siteMapBeans.add(siteMapBean);
185             }
186         }
187     }
188 
189 
190     /**
191      * Search children of the root Node of type MgnlNodeType.NT_CONTENT and all inherited node types.
192      * @throws RepositoryException
193      */
194     private NodeIterator searchSiteChildNodes(Node root) throws RepositoryException {
195         NodeIterator nodes = null;
196 
197         NodeTypePredicate predicate = new NodeTypePredicate(NodeTypes.Content.NAME, true);
198         nodes = new FilteringNodeIterator(root.getNodes(), predicate);
199 
200         return nodes;
201     }
202 
203 
204     /**
205      * Create a new SiteMapEntry POJO for all node from the NodeIterator if required.
206      */
207     private void feedSiteMapBeans(List<SiteMapEntry> siteMapBeans, NodeIterator nodes,  boolean isForEdit, Boolean inheritedParentDisplayColor) throws RepositoryException {
208 
209         while(nodes.hasNext()) {
210             // Init
211             Node child = nodes.nextNode();
212             Site site = siteManager.getAssignedSite(child);
213 
214             if(hasToBePopulated(child, false, isForEdit, site)) {
215                 //Populate the Bean used by the display
216                 SiteMapEntry siteMapBean = populateBean(child, false, isForEdit , site, inheritedParentDisplayColor);
217                 NodeIterator childNodes = searchSiteChildNodes(child);
218                 boolean hasChild = (childNodes!=null && childNodes.hasNext()) ? true:false;
219 
220                 if(isForEdit) {
221                     // Handle Edit Mode. Always all the bean.
222                     siteMapBeans.add(siteMapBean);
223 
224                     if(hasChild) {
225                         // Handle the children if any
226                         Boolean hideNodeChildInGoogleSitemapTmp = null;
227                         // Handle the color of the child to be display.
228                         if(inheritedParentDisplayColor != null) {
229                             hideNodeChildInGoogleSitemapTmp = inheritedParentDisplayColor;
230                         }else if(hideNodeChildInGoogleSitemap(child)) {
231                             hideNodeChildInGoogleSitemapTmp = Boolean.TRUE;
232                         }
233 
234                         feedSiteMapBeans(siteMapBeans, childNodes, isForEdit, hideNodeChildInGoogleSitemapTmp);
235                     }
236 
237                 } else {
238                     // Handle XML Display Mode
239                     // If node has to be display.
240                     if (!hideNodeInGoogleSitemap(child)){
241                         // Check Multilang.
242                         if(i18nSupport.isEnabled() && site.getI18n().isEnabled()) {
243                             Locale currentLocale = i18nSupport.getLocale();
244                             for(Locale locale:site.getI18n().getLocales() ){
245                                 i18nSupport.setLocale(locale);
246                                 SiteMapEntry siteMapBeanLocale = populateBean(child, false, isForEdit, site, null);
247                                 if(siteMapBeanLocale != null) {
248                                     siteMapBeans.add(siteMapBeanLocale);
249                                 }
250                             }
251                             i18nSupport.setLocale(currentLocale);
252                         } else {
253                             siteMapBeans.add(siteMapBean);
254                         }
255                     }
256                     // Check if Child Node has to be included
257                     if(hasChild && !hideNodeChildInGoogleSitemap(child)) {
258                         // Call recursively
259                         feedSiteMapBeans(siteMapBeans, childNodes, isForEdit, null);
260                     }
261 
262                 }
263 
264             }
265 
266         }
267     }
268 
269     /**
270      * Populate the Bean.
271      */
272     private SiteMapEntry populateBean( Node child, boolean isForUriMapping, boolean isForEdit, Site site, Boolean inheritedParentDisplayColor ) throws RepositoryException {
273         SiteMapEntry res = null;
274         // Populate only if true
275         if(hasToBePopulated(child, isForUriMapping, isForEdit, site)) {
276 
277             String lastmod = "";
278             int level = -1;
279             String loc = null;
280             double priority = DEFAULT_PRIORITY;
281             String changefreq = DEFAULT_CHANGE_FREQUENCY;
282             // Get Level
283             level = child.getDepth();
284 
285             // Get priority
286             if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_PRIORITY_NODEDATA)) {
287                 priority = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_PRIORITY_NODEDATA).getDouble();
288             }
289 
290             // Get changefreq
291             if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_CHANGEFREQ_NODEDATA)) {
292                 changefreq = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_CHANGEFREQ_NODEDATA).getString();
293             }
294 
295             // Get lastmod
296             Calendar date = null;
297             if (NodeTypes.LastModified.getLastModified(child) != null) {
298                 date = NodeTypes.LastModified.getLastModified(child);
299             } else {
300                 date = NodeTypes.Created.getCreated(child);
301             }
302             lastmod = configuration.getFastDateFormat().format(date.getTime());
303 
304             // Get loc
305             if(isForUriMapping) {
306                 loc = child.hasProperty("fromURI")? MgnlContext.getContextPath()+child.getProperty("fromURI").getString() : "";
307             }else {
308                 loc = LinkUtil.createExternalLink(child);
309             }
310 
311             //Populate the bean:
312             res = new SiteMapEntry(loc, lastmod, changefreq, Double.toString(priority), level);
313             log.debug("Populate Basic info for Node: "+child.getPath()+ " with values "+res.toStringDisplay());
314 
315             if(isForEdit) {
316                 res = populateBeanForEdit(child, isForUriMapping, res, inheritedParentDisplayColor);
317             }
318         }
319 
320         return res;
321     }
322 
323 
324     /**
325      * Add additional info's for Edit.
326      */
327     private SiteMapEntry populateBeanForEdit(Node child, boolean isForUriMapping, SiteMapEntry siteMapBean, Boolean inheritedParentDisplayColor) throws RepositoryException {
328         //Common fields
329         siteMapBean.setPath(child.getPath());
330 
331         if(isForUriMapping) {
332             //For VirtualUri
333             siteMapBean.setFrom(child.hasProperty("fromURI")?child.getProperty("fromURI").getString():"");
334             siteMapBean.setTo(child.hasProperty("toURI")?child.getProperty("toURI").getString():"");
335             siteMapBean.setStyleAlert(hideNodeInGoogleSitemap(child));
336             log.debug("Populate Edit VirtualUri info for Node: "+child.getPath()+ " with values "+siteMapBean.toStringVirtualUri());
337         } else {
338             //For Site
339             siteMapBean.setPageName(child.hasProperty("title")?child.getProperty("title").getString():"");
340             siteMapBean.setStyleAlert(inheritedParentDisplayColor!=null?inheritedParentDisplayColor.booleanValue():hideNodeInGoogleSitemap(child));
341             log.debug("Populate Edit Site info for Node: "+child.getPath()+ " with values "+siteMapBean.toStringSite());
342         }
343         return siteMapBean;
344     }
345 
346 
347     /**
348      * Check if the node has to be populated.
349      * True for UriMapping if:
350      *  !hideInGoogleSitemap && node has a modification date
351      * True for Site if:
352      *  !hideInGoogleSitemap && !hideInGoogleSitemapChildren &&  node has a creation date && site.isEnabled()
353      *
354      */
355     private boolean hasToBePopulated( Node child, boolean isForUriMapping, boolean isForEdit, Site site ) throws RepositoryException {
356         boolean hasCreationDate = NodeTypes.Created.getCreated(child) != null;
357         boolean hasModificationDate = NodeTypes.LastModified.getLastModified(child) != null;
358         boolean hasTemplate = StringUtils.isNotBlank(NodeTypes.Renderable.getTemplate(child));
359 
360         // Exclude SiteMap pages
361         if (hasTemplate && NodeTypes.Renderable.getTemplate(child).endsWith(GoogleSiteMapConfiguration.SITE_MAP_TEMPLATE_NAME)) {
362             return false;
363         }
364 
365         // For edit and if has creation date --> true.
366         if(isForEdit) {
367             return hasCreationDate;
368         }
369 
370         // Node has a property set to hide node to be display --> false.
371         if(hideNodeInGoogleSitemap(child) && (isForUriMapping || hideNodeChildInGoogleSitemap(child))) {
372             return false;
373         }
374         // For uriMapping.
375         if(isForUriMapping  && hasModificationDate) {
376             return true;
377         }
378         // For site.
379         if(!isForUriMapping && site.isEnabled() && hasCreationDate ) {
380             return true;
381         }
382 
383         return false;
384     }
385 
386     /**
387      * Check if we have to display in SiteMap.
388      */
389     private boolean hideNodeInGoogleSitemap(Node child) throws RepositoryException {
390         boolean res = false;
391 
392         if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAP_NODEDATA)) {
393             res = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAP_NODEDATA).getBoolean();
394         }
395 
396         return res;
397     }
398 
399     /**
400      * Check if we have to display in SiteMap.
401      */
402     private boolean hideNodeChildInGoogleSitemap(Node child) throws RepositoryException {
403         boolean res = false;
404 
405         if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAPCHILDREN_NODEDATA)) {
406             res = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAPCHILDREN_NODEDATA).getBoolean();
407         }
408 
409         return res;
410     }
411 
412 
413     /**
414      * Get Site and virtualUri informations if they are defined as components.
415      * Notice that by using a HashSet, a single object will be keeped in case of equality of the location.
416      */
417     public Iterator<SiteMapEntry> getSiteMapBeans(Node content) throws RepositoryException {
418         Set<SiteMapEntry> res = new HashSet<SiteMapEntry>();
419         // Return if no content set.
420         if (!NodeUtil.isNodeType(content, GoogleSiteMapConfiguration.NODE_TYPE)) {
421             return res.iterator();
422         }
423 
424         // Display Site informations part... if defined
425         Node node = null;
426         Iterator<Node> iterator = NodeUtil.collectAllChildren(content, All_Site_Nodes).iterator();
427         while (iterator.hasNext()) {
428             // Get node
429             node = iterator.next();
430 
431             Node childNode = new JCRMgnlPropertiesFilteringNodeWrapper(node);
432             PropertyIterator properties = childNode.getProperties();
433 
434             while (properties.hasNext()) {
435                 Property property = properties.nextProperty();
436                 Node root = SessionUtil.getNode(RepositoryConstants.WEBSITE, property.getString());
437                 // Call service and Remove Duplicate
438                 res.addAll(getSiteMapBeanForSite(root, false));
439 
440             }
441         }
442 
443         boolean includeVirtualUris =
444             content.hasProperty(GoogleSiteMapConfiguration.INCLUDE_VIRTUAL_URI_MAPPINGS_PROPERTIES) &&
445             content.getProperty(GoogleSiteMapConfiguration.INCLUDE_VIRTUAL_URI_MAPPINGS_PROPERTIES).getBoolean();
446         if (includeVirtualUris) {
447             res.addAll(getSiteMapBeanForVirtualUri(false));
448         }
449         return res.iterator();
450     }
451 
452 }