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