View Javadoc
1   /**
2    * This file Copyright (c) 2003-2018 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;
35  
36  import info.magnolia.jcr.util.NodeTypes;
37  import info.magnolia.jcr.util.PropertyUtil;
38  import info.magnolia.module.delta.AbstractRepositoryTask;
39  import info.magnolia.module.delta.Condition;
40  import info.magnolia.module.delta.Delta;
41  import info.magnolia.module.delta.DeltaBuilder;
42  import info.magnolia.module.delta.ModuleFilesExtraction;
43  import info.magnolia.module.delta.Task;
44  import info.magnolia.module.delta.TaskExecutionException;
45  import info.magnolia.module.model.ModuleDefinition;
46  import info.magnolia.module.model.Version;
47  import info.magnolia.module.model.VersionComparator;
48  
49  import java.util.ArrayList;
50  import java.util.Collections;
51  import java.util.LinkedList;
52  import java.util.List;
53  import java.util.Map;
54  import java.util.TreeMap;
55  
56  import javax.jcr.Node;
57  import javax.jcr.Property;
58  import javax.jcr.RepositoryException;
59  import javax.jcr.Session;
60  
61  import org.apache.commons.lang3.StringUtils;
62  import org.slf4j.Logger;
63  import org.slf4j.LoggerFactory;
64  
65  /**
66   * Extend this and register your deltas in the constructor using the register method.
67   * Add your own install tasks by overriding the getExtraInstallTasks() method.
68   * In most cases, modules won't need to override any other method.
69   *
70   * @see info.magnolia.module.DefaultModuleVersionHandler
71   */
72  public abstract class AbstractModuleVersionHandler implements ModuleVersionHandler {
73  
74      protected Logger log = LoggerFactory.getLogger(getClass());
75  
76      private final Map<Version, Delta> allDeltas;
77  
78      public AbstractModuleVersionHandler() {
79          allDeltas = new TreeMap<>(new VersionComparator());
80      }
81  
82      /**
83       * Registers the delta needed to update to version v from the previous one.
84       * Adds a Task to update the module version in the repository.
85       */
86      protected void register(Delta delta) {
87          final Version v = delta.getVersion();
88          if (allDeltas.containsKey(v)) {
89              throw new IllegalStateException("Version " + v + " was already registered in this ModuleVersionHandler.");
90          }
91          delta.getTasks().addAll(getDefaultUpdateTasks(v));
92          delta.getConditions().addAll(getDefaultUpdateConditions(v));
93          allDeltas.put(v, delta);
94      }
95  
96      @Override
97      public Version getCurrentlyInstalled(InstallContext ctx) {
98          try {
99              log.debug("checking currently installed version of module [{}]", ctx.getCurrentModuleDefinition());
100 
101             // check if this module was ever installed
102             if (!ctx.hasModulesNode()) {
103                 return null;
104             }
105             final Node moduleNode = ctx.getOrCreateCurrentModuleNode();
106             if (!moduleNode.hasProperty("version")) {
107                 return null;
108             }
109 
110             final Property versionProp = moduleNode.getProperty("version");
111             return Version.parseVersion(versionProp.getString());
112         } catch (RepositoryException e) {
113             throw new RuntimeException(e); // TODO
114         }
115     }
116 
117     @Override
118     public List<Delta> getDeltas(InstallContext installContext, Version from) {
119         if (from == null) {
120             return Collections.singletonList(getInstall(installContext));
121         }
122 
123         return getUpdateDeltas(installContext, from);
124     }
125 
126     protected List<Delta> getUpdateDeltas(InstallContext installContext, Version from) {
127         final Version toVersion = installContext.getCurrentModuleDefinition().getVersion();
128         final List<Delta> deltas = new LinkedList<>();
129         for (Version v : allDeltas.keySet()) {
130             if (v.isStrictlyAfter(toVersion)) {
131                 throw new IllegalArgumentException("Cannot handle delta for version '" + v.toString() + "' while only installing version '" + toVersion.toString() + "' of module '" + installContext.getCurrentModuleDefinition().getName() + "'.");
132             }
133             if (v.isStrictlyAfter(from)) {
134                 final Delta delta = allDeltas.get(v);
135                 if (v.isEquivalent(toVersion) && !StringUtils.equals(v.getClassifier(), toVersion.getClassifier())) {
136                     delta.getTasks().add(new ModuleVersionUpdateTask(toVersion));
137                 }
138                 deltas.add(delta);
139             }
140         }
141 
142         // if there was no delta for the version being installed, we still need to add the default update tasks
143         if (toVersion.isStrictlyAfter(from) && !allDeltas.containsKey(toVersion)) {
144             deltas.add(getDefaultUpdate(installContext));
145         }
146         return deltas;
147     }
148 
149     /**
150      * The minimal delta to be applied for each update, even if no delta was specifically registered
151      * for the version being installed.
152      */
153     protected Delta getDefaultUpdate(InstallContext installContext) {
154         final Version toVersion = installContext.getCurrentModuleDefinition().getVersion();
155         final List<Task> defaultUpdateTasks = getDefaultUpdateTasks(toVersion);
156         final List<Condition> defaultUpdateConditions = getDefaultUpdateConditions(toVersion);
157         return DeltaBuilder.update(toVersion, "").addTasks(defaultUpdateTasks).addConditions(defaultUpdateConditions);
158     }
159 
160     protected List<Task> getDefaultUpdateTasks(Version forVersion) {
161         final List<Task> defaultUpdates = new ArrayList<>(2);
162         defaultUpdates.add(new ModuleFilesExtraction());
163         defaultUpdates.add(new ModuleVersionUpdateTask(forVersion));
164         return defaultUpdates;
165     }
166 
167     protected List<Condition> getDefaultUpdateConditions(Version forVersion) {
168         return Collections.emptyList();
169     }
170 
171     /**
172      * @see #getBasicInstallTasks(InstallContext) override this method if you need a different set of default install tasks.
173      * @see #getExtraInstallTasks(InstallContext) override this method if you need extra tasks for install.
174      */
175     protected Delta getInstall(InstallContext installContext) {
176         final List<Task> installTasks = new ArrayList<>();
177         installTasks.addAll(getBasicInstallTasks(installContext));
178         installTasks.addAll(getExtraInstallTasks(installContext));
179         installTasks.add(new ModuleVersionToLatestTask());
180         final List<Condition> conditions = getInstallConditions();
181         final Version version = installContext.getCurrentModuleDefinition().getVersion();
182         return DeltaBuilder.install(version, "").addTasks(installTasks).addConditions(conditions);
183     }
184 
185     protected abstract List<Task> getBasicInstallTasks(InstallContext installContext);
186 
187     /**
188      * Override this method to add specific install tasks to your module.
189      * Returns an empty list by default.
190      */
191     protected List<Task> getExtraInstallTasks(InstallContext installContext) {
192         return Collections.emptyList();
193     }
194 
195     protected List<Condition> getInstallConditions() {
196         return Collections.emptyList();
197     }
198 
199     @Override
200     public Delta getStartupDelta(InstallContext installContext) {
201         final ModuleDefinition moduleDef = installContext.getCurrentModuleDefinition();
202         final List<Task> tasks = getStartupTasks(installContext);
203         return DeltaBuilder.startup(moduleDef, tasks);
204     }
205 
206     /**
207      * Override this method to add specific startup tasks to your module.
208      * Returns an empty list by default.
209      */
210     protected List<Task> getStartupTasks(InstallContext installContext) {
211         return Collections.emptyList();
212     }
213 
214     /**
215      * The task which modifies the "version" property to the version being <strong>installed</strong>.
216      * (from module descriptor)
217      * TODO : make this mandatory and "hidden" ?
218      */
219     public class ModuleVersionToLatestTask extends AbstractRepositoryTask {
220         protected ModuleVersionToLatestTask() {
221             super("Version number", "Sets installed module version number.");
222         }
223 
224         @Override
225         protected void doExecute(InstallContext ctx) throws RepositoryException, TaskExecutionException {
226             // make sure we have the /modules node
227             if (!ctx.hasModulesNode()) {
228                 final Session session = ctx.getConfigJCRSession();
229                 session.getRootNode().addNode(ModuleManagerImpl.MODULES_NODE, NodeTypes.Content.NAME);
230             }
231 
232             final Node moduleNode = ctx.getOrCreateCurrentModuleNode();
233             PropertyUtil.setProperty(moduleNode, "version", getVersion(ctx).toString());
234         }
235 
236         protected Version getVersion(InstallContext ctx) {
237             return ctx.getCurrentModuleDefinition().getVersion();
238         }
239     }
240 
241     /**
242      * The task which modifies the "version" property to the version we're <strong>updating</strong> to.
243      */
244     public class ModuleVersionUpdateTask extends ModuleVersionToLatestTask {
245         private final Version toVersion;
246 
247         protected ModuleVersionUpdateTask(Version toVersion) {
248             super();
249             this.toVersion = toVersion;
250         }
251 
252         @Override
253         protected Version getVersion(InstallContext ctx) {
254             return toVersion;
255         }
256     }
257 
258 }