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.core.MetaData;
37  import info.magnolia.cms.core.MgnlNodeType;
38  import info.magnolia.cms.i18n.I18nContentSupport;
39  import info.magnolia.cms.i18n.I18nContentSupportFactory;
40  import info.magnolia.cms.util.ContentUtil;
41  import info.magnolia.context.MgnlContext;
42  import info.magnolia.jcr.util.MetaDataUtil;
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.repository.RepositoryConstants;
50  
51  import java.util.ArrayList;
52  import java.util.Calendar;
53  import java.util.List;
54  import java.util.Locale;
55  
56  import javax.inject.Inject;
57  import javax.inject.Singleton;
58  import javax.jcr.Node;
59  import javax.jcr.NodeIterator;
60  import javax.jcr.RepositoryException;
61  
62  import org.apache.jackrabbit.commons.iterator.FilteringNodeIterator;
63  import org.apache.jackrabbit.commons.predicate.NodeTypePredicate;
64  import org.slf4j.Logger;
65  import org.slf4j.LoggerFactory;
66  
67  
68  /**
69   * Main SiteMapService.
70   * This service is responsible for:
71   *   Searching the appropriate nodes (pages or VirtualUri) that should be displayed (for XML rendering or Editing).
72   *   From the search nodes, create SiteMapEntrys (POJO containing preformated infos used for the rendering).
73   *
74   * @version $Id$
75   *
76   */
77  @Singleton
78  public class SiteMapService {
79  
80      private static final Logger log = LoggerFactory.getLogger(SiteMapService.class);
81      /**
82       * Injected service.
83       */
84      private SiteManager siteManager;
85      private I18nContentSupport i18nSupport;
86      private GoogleSiteMapConfiguration configuration;
87      private QueryUtil queryUtil;
88      /**
89       * Global variable.
90       */
91      private String lastMod = RepositoryConstants.NAMESPACE_PREFIX + ":" + MetaData.LAST_MODIFIED;
92      private String creation = RepositoryConstants.NAMESPACE_PREFIX + ":" + MetaData.CREATION_DATE;
93  
94  
95      /**
96       * Constructor for injection.
97       * @param siteManager: Injected.
98       */
99      @Inject
100     public SiteMapService(SiteManager siteManager, GoogleSiteMapConfiguration configuration, QueryUtil queryUtil) {
101         this.siteManager = siteManager;
102         this.configuration = configuration;
103         this.queryUtil = queryUtil;
104         i18nSupport = I18nContentSupportFactory.getI18nSupport();
105     }
106 
107 
108     /**
109      * Create the SiteMapEntry List corresponding to the uriMapping.
110      */
111     public List<SiteMapEntry> getSiteMapBeanForVirtualUri( boolean isForEdit) throws RepositoryException{
112         // Init
113         List<SiteMapEntry> res = new ArrayList<SiteMapEntry>();
114         NodeIterator nodeIterator = null;
115 
116         log.info("Requested virtualUri info's for EDIT ");
117         // Create a virtualUri info beans
118         nodeIterator = searchVirtualUriNodes();
119         feedVirtualUriMapBeans(res, nodeIterator, isForEdit);
120 
121         return res;
122     }
123 
124     /**
125      * Search the nodes related to the UriMappings.
126      */
127     private NodeIterator searchVirtualUriNodes() {
128         NodeIterator nodes = null;
129 
130         String xpath = "//virtualURIMapping//element(*,"+MgnlNodeType.NT_CONTENTNODE+")";
131         nodes = queryUtil.query(RepositoryConstants.CONFIG, xpath, "xpath", MgnlNodeType.NT_CONTENTNODE);
132 
133         return nodes;
134     }
135 
136     /**
137      * Extract informations form the Node and create a Bean used in the display
138      * for Edit and XML rendering.
139      */
140     private void feedVirtualUriMapBeans(List<SiteMapEntry> siteMapBeans, NodeIterator nodes, boolean isForEdit) throws RepositoryException {
141         while(nodes.hasNext()) {
142             // Init
143             Node child = nodes.nextNode();
144             if(hasToBePopulated(child, true, isForEdit, null)) {
145                 //Populate the Bean used by the display
146                 SiteMapEntry siteMapBean = populateBean(child, true, isForEdit , null, null);
147                 //Add the bean to the result
148                 siteMapBeans.add(siteMapBean);
149             }
150         }
151     }
152 
153 
154     /**
155      * Create the SiteMapEntry for all child's of the rootNode that are of type MgnlNodeType.NT_CONTENT.
156      */
157     public List<SiteMapEntry> getSiteMapBeanForSite(Node rootNode, boolean isForEdit) throws RepositoryException{
158         // Init
159         List<SiteMapEntry> res = new ArrayList<SiteMapEntry>();
160         NodeIterator nodeIterator = null;
161 
162         log.info("Requested siteMap info's for EDIT based on the following root: "+rootNode.getPath());
163         // Create a site info beans
164         nodeIterator = searchSiteChildNodes(rootNode);
165         feedSiteMapBeans(res, nodeIterator, isForEdit, null);
166 
167         return res;
168     }
169 
170 
171     /**
172      * Search children of the root Node of type MgnlNodeType.NT_CONTENT and all inherited node types.
173      * @throws RepositoryException
174      */
175     private NodeIterator searchSiteChildNodes(Node root) throws RepositoryException {
176         NodeIterator nodes = null;
177 
178         NodeTypePredicate predicate = new NodeTypePredicate(MgnlNodeType.NT_CONTENT, true);
179         nodes = new FilteringNodeIterator(root.getNodes(), predicate);
180 
181         return nodes;
182     }
183 
184 
185     /**
186      * Create a new SiteMapEntry POJO for all node from the NodeIterator if required.
187      */
188     private void feedSiteMapBeans(List<SiteMapEntry> siteMapBeans, NodeIterator nodes,  boolean isForEdit, Boolean inheritedParentDisplayColor) throws RepositoryException {
189 
190         while(nodes.hasNext()) {
191             // Init
192             Node child = nodes.nextNode();
193             Site site = siteManager.getAssignedSite(child);
194 
195             if(hasToBePopulated(child, false, isForEdit, site)) {
196                 //Populate the Bean used by the display
197                 SiteMapEntry siteMapBean = populateBean(child, false, isForEdit , site, inheritedParentDisplayColor);
198                 NodeIterator childNodes = searchSiteChildNodes(child);
199                 boolean hasChild = (childNodes!=null && childNodes.hasNext()) ? true:false;
200 
201                 if(isForEdit) {
202                     // Handle Edit Mode. Always all the bean.
203                     siteMapBeans.add(siteMapBean);
204 
205                     if(hasChild) {
206                         // Handle the children if any
207                         Boolean hideNodeChildInGoogleSitemapTmp = null;
208                         // Handle the color of the child to be display.
209                         if(inheritedParentDisplayColor != null) {
210                             hideNodeChildInGoogleSitemapTmp = inheritedParentDisplayColor;
211                         }else if(hideNodeChildInGoogleSitemap(child)) {
212                             hideNodeChildInGoogleSitemapTmp = Boolean.TRUE;
213                         }
214 
215                         feedSiteMapBeans(siteMapBeans, childNodes, isForEdit, hideNodeChildInGoogleSitemapTmp);
216                     }
217 
218                 } else {
219                     // Handle XML Display Mode
220                     // If node has to be display.
221                     if (!hideNodeInGoogleSitemap(child)){
222                         // Check Multilang.
223                         if(i18nSupport.isEnabled() && site.getI18n().isEnabled()) {
224                             Locale currentLocale = i18nSupport.getLocale();
225                             for(Locale locale:site.getI18n().getLocales() ){
226                                 i18nSupport.setLocale(locale);
227                                 SiteMapEntry siteMapBeanLocale = populateBean(child, false, isForEdit, site, null);
228                                 if(siteMapBeanLocale != null) {
229                                     siteMapBeans.add(siteMapBeanLocale);
230                                 }
231                             }
232                             i18nSupport.setLocale(currentLocale);
233                         } else {
234                             siteMapBeans.add(siteMapBean);
235                         }
236                     }
237                     // Check if Child Node has to be included
238                     if(hasChild && !hideNodeChildInGoogleSitemap(child)) {
239                         // Call recursively
240                         feedSiteMapBeans(siteMapBeans, childNodes, isForEdit, null);
241                     }
242 
243                 }
244 
245             }
246 
247         }
248     }
249 
250     /**
251      * Populate the Bean.
252      */
253     private SiteMapEntry populateBean( Node child, boolean isForUriMapping, boolean isForEdit, Site site, Boolean inheritedParentDisplayColor ) throws RepositoryException {
254         SiteMapEntry res = null;
255         // Populate only if true
256         if(hasToBePopulated(child, isForUriMapping, isForEdit, site)) {
257             double priority = 0.5;
258             String changefreq = "weekly";
259             String lastmod = "";
260             int level = -1;
261             String loc = null;
262 
263             // Get Level
264             level = child.getDepth();
265 
266             // Get priority
267             if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_PRIORITY_NODEDATA)) {
268                 priority = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_PRIORITY_NODEDATA).getDouble();
269             }
270 
271             // Get changefreq
272             if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_CHANGEFREQ_NODEDATA)) {
273                 changefreq = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_CHANGEFREQ_NODEDATA).getString();
274             }
275 
276             // Get lastmod
277             Node metaData = child.getNode(MetaData.DEFAULT_META_NODE);
278             Calendar date = null;
279             if(metaData.hasProperty(lastMod)) {
280                 date = metaData.getProperty(lastMod).getDate();
281             } else {
282                 date = metaData.getProperty(creation).getDate();
283             }
284             lastmod = configuration.getFastDateFormat().format(date.getTime());
285 
286             // Get loc
287             if(isForUriMapping) {
288                 loc = MgnlContext.getContextPath()+child.getProperty("fromURI").getString();
289             }else {
290                 loc = LinkUtil.createExternalLink(ContentUtil.asContent(child));
291             }
292 
293             //Populate the bean:
294             res = new SiteMapEntry(loc, lastmod, changefreq, Double.toString(priority), level);
295             log.debug("Populate Basic info for Node: "+child.getPath()+ " with values "+res.toStringDisplay());
296 
297             if(isForEdit) {
298                 res = populateBeanForEdit(child, isForUriMapping, res, inheritedParentDisplayColor);
299             }
300         }
301 
302         return res;
303     }
304 
305 
306     /**
307      * Add additional info's for Edit.
308      */
309     private SiteMapEntry populateBeanForEdit(Node child, boolean isForUriMapping, SiteMapEntry siteMapBean, Boolean inheritedParentDisplayColor) throws RepositoryException {
310         //Common fields
311         siteMapBean.setPath(child.getPath());
312 
313         if(isForUriMapping) {
314             //For VirtualUri
315             siteMapBean.setFrom(child.hasProperty("fromURI")?child.getProperty("fromURI").getString():"");
316             siteMapBean.setTo(child.hasProperty("toURI")?child.getProperty("toURI").getString():"");
317             siteMapBean.setStyleAlert(hideNodeInGoogleSitemap(child));
318             log.debug("Populate Edit VirtualUri info for Node: "+child.getPath()+ " with values "+siteMapBean.toStringVirtualUri());
319         } else {
320             //For Site
321             siteMapBean.setPageName(child.hasProperty("title")?child.getProperty("title").getString():"");
322             siteMapBean.setStyleAlert(inheritedParentDisplayColor!=null?inheritedParentDisplayColor.booleanValue():hideNodeInGoogleSitemap(child));
323             log.debug("Populate Edit Site info for Node: "+child.getPath()+ " with values "+siteMapBean.toStringSite());
324         }
325         return siteMapBean;
326     }
327 
328 
329     /**
330      * Check if the node has to be populated.
331      * True for UriMapping if:
332      *  !hideInGoogleSitemap && node has a modification date
333      * True for Site if:
334      *  !hideInGoogleSitemap && !hideInGoogleSitemapChildren &&  node has a creation date && site.isEnabled()
335      *
336      */
337     private boolean hasToBePopulated( Node child, boolean isForUriMapping, boolean isForEdit, Site site ) throws RepositoryException {
338         boolean hasCreationDate = child.hasNode(MetaData.DEFAULT_META_NODE) && child.getNode(MetaData.DEFAULT_META_NODE).hasProperty(creation);
339         boolean hasModificationDate = child.hasNode(MetaData.DEFAULT_META_NODE) && child.getNode(MetaData.DEFAULT_META_NODE).hasProperty(lastMod);
340 
341         // Exclude SiteMap pages
342         if(MetaDataUtil.getTemplate(child).endsWith(GoogleSiteMapConfiguration.SITE_MAP_TEMPLATE_NAME)) {
343             return false;
344         }
345 
346         // For edit and if has creation date --> true.
347         if(isForEdit) {
348             return hasCreationDate;
349         }
350 
351         // Node has a property set to hide node to be display --> false.
352         if(hideNodeInGoogleSitemap(child) && (isForUriMapping || hideNodeChildInGoogleSitemap(child))) {
353             return false;
354         }
355         // For uriMapping.
356         if(isForUriMapping  && hasModificationDate) {
357             return true;
358         }
359         // For site.
360         if(!isForUriMapping && site.isEnabled() && hasCreationDate ) {
361             return true;
362         }
363 
364         return false;
365     }
366 
367     /**
368      * Check if we have to display in SiteMap.
369      */
370     private boolean hideNodeInGoogleSitemap(Node child) throws RepositoryException {
371         boolean res = false;
372 
373         if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAP_NODEDATA)) {
374             res = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAP_NODEDATA).getBoolean();
375         }
376 
377         return res;
378     }
379 
380     /**
381      * Check if we have to display in SiteMap.
382      */
383     private boolean hideNodeChildInGoogleSitemap(Node child) throws RepositoryException {
384         boolean res = false;
385 
386         if(child.hasProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAPCHILDREN_NODEDATA)) {
387             res = child.getProperty(GoogleSiteMapConfiguration.DEFAULT_HIDEINGOOGLESITEMAPCHILDREN_NODEDATA).getBoolean();
388         }
389 
390         return res;
391     }
392 
393 }