View Javadoc

1   /**
2    * This file Copyright (c) 2013 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.setup.migration;
35  
36  import info.magnolia.cms.util.QueryUtil;
37  import info.magnolia.jcr.util.NodeTypes;
38  import info.magnolia.jcr.util.NodeUtil;
39  import info.magnolia.jcr.wrapper.JCRMgnlPropertiesFilteringNodeWrapper;
40  import info.magnolia.jcr.wrapper.JCRPropertiesFilteringNodeWrapper;
41  import info.magnolia.module.InstallContext;
42  import info.magnolia.module.delta.AbstractRepositoryTask;
43  import info.magnolia.module.delta.TaskExecutionException;
44  import info.magnolia.module.googlesitemap.GoogleSiteMapConfiguration;
45  import info.magnolia.module.googlesitemap.config.SiteMapType;
46  import info.magnolia.repository.RepositoryConstants;
47  
48  import java.util.ArrayList;
49  import java.util.Iterator;
50  import java.util.List;
51  
52  import javax.jcr.Node;
53  import javax.jcr.NodeIterator;
54  import javax.jcr.Property;
55  import javax.jcr.PropertyIterator;
56  import javax.jcr.RepositoryException;
57  import javax.jcr.Session;
58  import javax.jcr.query.Query;
59  
60  import org.apache.commons.lang.StringUtils;
61  import org.slf4j.Logger;
62  import org.slf4j.LoggerFactory;
63  
64  
65  /**
66   * Google site map dedicated migration task.<br>
67   * Implemented logic: <br>
68   * - Search all siteMaps definition from the specified workspace.<br>
69   * - For every siteMapDefinition <br>
70   * -- Create a folders representing the siteMap definition path (demo-project/about/siteMapDefinitoin --> demo-project/about) <br>
71   * -- Create a new M5 siteMap definition based on the M4.5 one.<br>
72   * -- Remove the M4.5 siteMap definition.<br>
73   */
74  public class SiteMapDefinitionMigrationTask extends AbstractRepositoryTask {
75  
76      private final Logger log = LoggerFactory.getLogger(SiteMapDefinitionMigrationTask.class);
77  
78      private final String sourceWorkspace;
79      private final String searchRootPath;
80      private final String templateName = "google-sitemap:pages/siteMapsConfiguration";
81      private final String siteDefinitionTemplateName = "google-sitemap:components/content/siteComponent";
82      private final String virtualUriTemplateName = "google-sitemap:components/content/virtualUriComponent";
83      private Session siteMapSession;
84  
85  
86      public SiteMapDefinitionMigrationTask(String name, String description, String sourceWorkspace, String searchRootPath) {
87          super(name, description);
88          this.sourceWorkspace = (StringUtils.isNotBlank(sourceWorkspace)) ? sourceWorkspace : RepositoryConstants.WEBSITE;
89          this.searchRootPath = (StringUtils.isNotBlank(searchRootPath)) ? searchRootPath : "/";
90      }
91  
92      @Override
93      protected void doExecute(InstallContext installContext) throws RepositoryException, TaskExecutionException {
94          // Init
95          siteMapSession = installContext.getJCRSession(GoogleSiteMapConfiguration.WORKSPACE);
96          try {
97              // Perform the query
98              NodeIterator nodeIterator = getQueryResult();
99              // Handle result
100             while (nodeIterator.hasNext()) {
101                 handleSimeMapDefinitionMigration(nodeIterator.nextNode());
102             }
103 
104         } catch (RepositoryException re) {
105             installContext.error("Unable to perform Migration task " + getName(), re);
106             throw new TaskExecutionException(re.getMessage());
107         }
108     }
109 
110     private void handleSimeMapDefinitionMigration(Node siteMapNodeDefinition) throws RepositoryException {
111         // Create path in the siteMap workspace
112         Node targetRootSiteMapFolder = getOrCreateRootSiteMapDefinitionNode(siteMapNodeDefinition);
113 
114         // Create an equivalent M5 siteMap definition
115         Node targetSiteMapDefinition = migrateSiteMapDefinition(siteMapNodeDefinition, targetRootSiteMapFolder);
116         log.info("New M5 siteMapDefinition created {} based on the previous definition {} ", targetSiteMapDefinition.getPath(), siteMapNodeDefinition.getPath());
117 
118         // Remove the original siteMapDefinition
119         siteMapNodeDefinition.remove();
120     }
121 
122     /**
123      * Create a M5 siteMapDefinition based on a M4.5 definition.<br>
124      * Steps: <br>
125      * - Create a folder tree to the sitMapDefinition node if required<br>
126      * - Create a new siteMapDefinition nodeTye <br>
127      * - Create a new sites contentNode child
128      * - Copy all site definition from M4.5 into the newly created child node.
129      */
130     private Node migrateSiteMapDefinition(Node siteMapNodeDefinition, Node targetRootSiteMapFolder) throws RepositoryException {
131         // Create the new M5 definition
132         Node targetSiteMapDefinition = targetRootSiteMapFolder.addNode(siteMapNodeDefinition.getName(), GoogleSiteMapConfiguration.NODE_TYPE);
133         // Copy all root properties
134         copyOrUpdateProperty(siteMapNodeDefinition, targetSiteMapDefinition);
135 
136         // Copy all properties from M4.5 definition into 5.2 format
137         populateSiteMapDefinition(targetSiteMapDefinition, siteMapNodeDefinition);
138 
139         return targetSiteMapDefinition;
140     }
141 
142     private void populateSiteMapDefinition(Node targetSiteMapDefinition, Node sourceSiteMapNodeDefinition) throws RepositoryException {
143         List<String> siteMapDefinitionList = new ArrayList<String>();
144         boolean insertVirtualUri = false;
145         Iterable<Node> children = NodeUtil.collectAllChildren(sourceSiteMapNodeDefinition);
146         Iterator<Node> childrenIterator = children.iterator();
147         // Iterate the child nodes and extract the configuration
148         while (childrenIterator.hasNext()) {
149             Node child = childrenIterator.next();
150             if (StringUtils.isNotBlank(NodeTypes.Renderable.getTemplate(child))) {
151                 String templateName = NodeTypes.Renderable.getTemplate(child);
152                 if (virtualUriTemplateName.equals(templateName)) {
153                     insertVirtualUri = true;
154                 } else if (siteDefinitionTemplateName.equals(templateName)) {
155                     siteMapDefinitionList.addAll(extractSiteMapDefinition(child));
156                 }
157             }
158         }
159 
160         // Set includeVirtualURIMappings property
161         targetSiteMapDefinition.setProperty(GoogleSiteMapConfiguration.INCLUDE_VIRTUAL_URI_MAPPINGS_PROPERTIES, insertVirtualUri);
162         // Set siteMaps definition
163         if (!siteMapDefinitionList.isEmpty()) {
164             Node sites = targetSiteMapDefinition.addNode("sites", NodeTypes.ContentNode.NAME);
165             int pos = 0;
166             for (String definition : siteMapDefinitionList) {
167                 sites.setProperty("" + pos, definition);
168                 pos += 1;
169             }
170         }
171     }
172 
173     private List<String> extractSiteMapDefinition(Node parent) throws RepositoryException {
174         List<String> siteDefinitions = new ArrayList<String>();
175         if (parent.hasNode(GoogleSiteMapConfiguration.SITE_DIALOG_CONFIGURATION_NAME)) {
176             Node filteredNode = new JCRMgnlPropertiesFilteringNodeWrapper(parent.getNode(GoogleSiteMapConfiguration.SITE_DIALOG_CONFIGURATION_NAME));
177             PropertyIterator iterator = filteredNode.getProperties();
178             while (iterator.hasNext()) {
179                 Property property = iterator.nextProperty();
180                 siteDefinitions.add(property.getString());
181             }
182         } else {
183             log.info("Node '{}' do not have a 'sites' child node.", parent.getPath());
184         }
185         return siteDefinitions;
186     }
187 
188 
189     /**
190      * Copy all non jcr: properties from the source to the target node.
191      */
192     private void copyOrUpdateProperty(Node source, Node target) throws RepositoryException {
193         Node filteredSource = new JCRPropertiesFilteringNodeWrapper(source);
194         PropertyIterator iterator = filteredSource.getProperties();
195         while (iterator.hasNext()) {
196             Property property = iterator.nextProperty();
197             if (target.hasProperty(property.getName())) {
198                 target.getProperty(property.getName()).setValue(property.getValue());
199             } else {
200                 target.setProperty(property.getName(), property.getValue());
201             }
202         }
203         // Set Url
204         target.setProperty(GoogleSiteMapConfiguration.SITE_MAP_URL_PROPERTY_NAME, source.getPath());
205         // Set default type
206         target.setProperty(GoogleSiteMapConfiguration.SITE_MAP_TYPE_PROPERTY_NAME, SiteMapType.Standard.name());
207         // Set display name
208         target.setProperty(GoogleSiteMapConfiguration.SITE_MAP_DISPLAY_NAME_PROPERTY_NAME, source.getName());
209     }
210 
211     /**
212      * Create the site map definition parent folder.
213      */
214     Node getOrCreateRootSiteMapDefinitionNode(Node siteMapNodeDefinition) throws RepositoryException {
215         // If the definition is at the root level, return the root node.
216         if (siteMapNodeDefinition.getDepth() <= 1) {
217             return siteMapSession.getRootNode();
218         }
219         // If the path already exist return the referenced node..
220         String targetPath = siteMapNodeDefinition.getParent().getPath();
221         if (siteMapSession.nodeExists(targetPath)) {
222             return siteMapSession.getNode(targetPath);
223         }
224         // If the path do not already exist, create it.
225         return NodeUtil.createPath(siteMapSession.getRootNode(), targetPath, NodeTypes.Folder.NAME, false);
226     }
227 
228     /**
229      * Search all node within 'sourceWorkspace' that are under the 'searchRootPath' containing a template definition equal to 'templateName'.
230      */
231     private NodeIterator getQueryResult() throws RepositoryException {
232         NodeIterator nodeIterator = null;
233         String query = "SELECT * FROM [nt:base] AS t WHERE (ISSAMENODE(t, '" + searchRootPath + "') OR ISDESCENDANTNODE(t, '" + searchRootPath + "')) " +
234                 "AND t.[mgnl:template] is not null AND contains(t.[" + NodeTypes.Renderable.TEMPLATE + "],'" + templateName + "')";
235         nodeIterator = QueryUtil.search(sourceWorkspace, query, Query.JCR_SQL2);
236         log.info("{} google site map definitions will be handled", nodeIterator.getSize());
237         return nodeIterator;
238     }
239 }