001/*
002 * (C) Copyright 2006-2017 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 *     Laurent Doguin
019 */
020package org.nuxeo.ecm.core.versioning;
021
022import java.io.Serializable;
023import java.util.ArrayDeque;
024import java.util.Deque;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.stream.Collectors;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.nuxeo.ecm.core.api.CoreSession;
033import org.nuxeo.ecm.core.api.DocumentModel;
034import org.nuxeo.ecm.core.api.VersioningOption;
035import org.nuxeo.ecm.core.model.Document;
036import org.nuxeo.runtime.api.Framework;
037import org.nuxeo.runtime.logging.DeprecationLogger;
038import org.nuxeo.runtime.model.ComponentContext;
039import org.nuxeo.runtime.model.ComponentInstance;
040import org.nuxeo.runtime.model.DefaultComponent;
041import org.nuxeo.runtime.model.SimpleContributionRegistry;
042
043/**
044 * Versioning service component and implementation.
045 */
046public class VersioningComponent extends DefaultComponent implements VersioningService {
047
048    private static final Log log = LogFactory.getLog(VersioningComponent.class);
049
050    public static final String VERSIONING_SERVICE_XP = "versioningService";
051
052    public static final String VERSIONING_RULE_XP = "versioningRules";
053
054    public static final String VERSIONING_POLICY_XP = "policies";
055
056    public static final String VERSIONING_FILTER_XP = "filters";
057
058    public static final String VERSIONING_RESTRICTION_XP = "restrictions";
059
060    protected static final StandardVersioningService STANDARD_VERSIONING_SERVICE = new StandardVersioningService();
061
062    protected Map<VersioningServiceDescriptor, VersioningService> versioningServices = new LinkedHashMap<>();
063
064    /**
065     * @deprecated since 9.1 use 'policy', 'filter' and 'restriction' contributions instead
066     */
067    @Deprecated
068    protected VersioningRuleRegistry versioningRulesRegistry = new VersioningRuleRegistry();
069
070    protected VersioningPolicyRegistry versioningPoliciesRegistry = new VersioningPolicyRegistry();
071
072    protected VersioningFilterRegistry versioningFiltersRegistry = new VersioningFilterRegistry();
073
074    protected VersioningRestrictionRegistry versioningRestrictionsRegistry = new VersioningRestrictionRegistry();
075
076    protected static class VersioningPolicyRegistry extends SimpleContributionRegistry<VersioningPolicyDescriptor> {
077
078        @Override
079        public String getContributionId(VersioningPolicyDescriptor contrib) {
080            return contrib.getId();
081        }
082
083        public Map<String, VersioningPolicyDescriptor> getVersioningPolicyDescriptors() {
084            return currentContribs.entrySet().stream().sorted(Map.Entry.comparingByValue()).collect(
085                    Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
086        }
087
088    }
089
090    protected static class VersioningFilterRegistry extends SimpleContributionRegistry<VersioningFilterDescriptor> {
091
092        @Override
093        public String getContributionId(VersioningFilterDescriptor contrib) {
094            return contrib.getId();
095        }
096
097        public Map<String, VersioningFilterDescriptor> getVersioningFilterDescriptors() {
098            return currentContribs;
099        }
100
101    }
102
103    protected static class VersioningRestrictionRegistry
104            extends SimpleContributionRegistry<VersioningRestrictionDescriptor> {
105
106        @Override
107        public String getContributionId(VersioningRestrictionDescriptor contrib) {
108            return contrib.getType();
109        }
110
111        public Map<String, VersioningRestrictionDescriptor> getVersioningRestrictionDescriptors() {
112            return currentContribs;
113        }
114
115    }
116
117    /**
118     * @deprecated since 9.1 use 'policy', 'filter' and 'restriction' contributions instead
119     */
120    @Deprecated
121    protected static class VersioningRuleRegistry extends SimpleContributionRegistry<VersioningRuleDescriptor> {
122
123        @Override
124        public String getContributionId(VersioningRuleDescriptor contrib) {
125            return contrib.getTypeName();
126        }
127
128        @Override
129        public VersioningRuleDescriptor clone(VersioningRuleDescriptor orig) {
130            return new VersioningRuleDescriptor(orig);
131        }
132
133        @Override
134        public void merge(VersioningRuleDescriptor src, VersioningRuleDescriptor dst) {
135            dst.merge(src);
136        }
137
138        @Override
139        public boolean isSupportingMerge() {
140            return true;
141        }
142
143        @Override
144        public void contributionUpdated(String id, VersioningRuleDescriptor contrib,
145                VersioningRuleDescriptor newOrigContrib) {
146            if (contrib.isEnabled()) {
147                currentContribs.put(id, contrib);
148            } else {
149                currentContribs.remove(id);
150            }
151        }
152
153        public void clear() {
154            currentContribs.clear();
155        }
156
157        public Map<String, VersioningRuleDescriptor> getVersioningRuleDescriptors() {
158            return currentContribs;
159        }
160    }
161
162    /**
163     * @deprecated since 9.1 use 'policy', 'filter' and 'restriction' contributions instead
164     */
165    @Deprecated
166    protected Deque<DefaultVersioningRuleDescriptor> defaultVersioningRuleList = new ArrayDeque<>();
167
168    // public for tests
169    public VersioningService service = null;
170
171    protected ComponentContext context;
172
173    @Override
174    public void activate(ComponentContext context) {
175        this.context = context;
176        this.service = STANDARD_VERSIONING_SERVICE;
177    }
178
179    @Override
180    public void deactivate(ComponentContext context) {
181        this.context = null;
182        this.service = null;
183    }
184
185    @Override
186    public void registerContribution(Object contrib, String point, ComponentInstance contributor) {
187        switch (point) {
188        case VERSIONING_SERVICE_XP:
189            registerVersioningService((VersioningServiceDescriptor) contrib);
190            break;
191        case VERSIONING_RULE_XP:
192            if (contrib instanceof VersioningRuleDescriptor) {
193                VersioningRuleDescriptor rule = (VersioningRuleDescriptor) contrib;
194                registerVersioningRule(rule);
195                String message = String.format(
196                        "Versioning rule for '%s' on component %s should now be contributed to extension points '%s', "
197                                + "'%s' and '%s': a compatibility registration was performed but it may not be accurate.",
198                        (rule).getTypeName(), contributor.getName(), VERSIONING_POLICY_XP, VERSIONING_FILTER_XP,
199                        VERSIONING_RESTRICTION_XP);
200                DeprecationLogger.log(message, "9.1");
201                Framework.getRuntime().getMessageHandler().addWarning(message);
202            } else if (contrib instanceof DefaultVersioningRuleDescriptor) {
203                registerDefaultVersioningRule((DefaultVersioningRuleDescriptor) contrib);
204                String message = String.format("Default versioning rule on component %s should now be contributed to "
205                        + "extension points '%s' and '%s': a compatibility registration was performed but it may not be "
206                        + "accurate.", contributor.getName(), VERSIONING_POLICY_XP, VERSIONING_RESTRICTION_XP);
207                DeprecationLogger.log(message, "9.1");
208                Framework.getRuntime().getMessageHandler().addWarning(message);
209            } else {
210                throw new RuntimeException("Unknown contribution to " + point + ": " + contrib.getClass());
211            }
212            break;
213        case VERSIONING_POLICY_XP:
214            registerVersioningPolicy((VersioningPolicyDescriptor) contrib);
215            break;
216        case VERSIONING_FILTER_XP:
217            registerVersioningFilter((VersioningFilterDescriptor) contrib);
218            break;
219        case VERSIONING_RESTRICTION_XP:
220            registerVersioningRestriction((VersioningRestrictionDescriptor) contrib);
221            break;
222        default:
223            throw new RuntimeException("Unknown extension point: " + point);
224        }
225    }
226
227    @Override
228    public void unregisterContribution(Object contrib, String point, ComponentInstance contributor) {
229        switch (point) {
230        case VERSIONING_SERVICE_XP:
231            unregisterVersioningService((VersioningServiceDescriptor) contrib);
232            break;
233        case VERSIONING_RULE_XP:
234            if (contrib instanceof VersioningRuleDescriptor) {
235                unregisterVersioningRule((VersioningRuleDescriptor) contrib);
236            } else if (contrib instanceof DefaultVersioningRuleDescriptor) {
237                unregisterDefaultVersioningRule((DefaultVersioningRuleDescriptor) contrib);
238            }
239            break;
240        case VERSIONING_POLICY_XP:
241            unregisterVersioningPolicy((VersioningPolicyDescriptor) contrib);
242            break;
243        case VERSIONING_FILTER_XP:
244            unregisterVersioningFilter((VersioningFilterDescriptor) contrib);
245            break;
246        case VERSIONING_RESTRICTION_XP:
247            unregisterVersioningRestriction((VersioningRestrictionDescriptor) contrib);
248            break;
249        default:
250            break;
251        }
252    }
253
254    protected void registerVersioningService(VersioningServiceDescriptor contrib) {
255        String klass = contrib.className;
256        try {
257            VersioningService vs = (VersioningService) context.getRuntimeContext().loadClass(klass).newInstance();
258            versioningServices.put(contrib, vs);
259        } catch (ReflectiveOperationException e) {
260            throw new RuntimeException("Failed to instantiate: " + klass, e);
261        }
262        log.info("Registered versioning service: " + klass);
263        recompute();
264    }
265
266    protected void unregisterVersioningService(VersioningServiceDescriptor contrib) {
267        versioningServices.remove(contrib);
268        log.info("Unregistered versioning service: " + contrib.className);
269        recompute();
270    }
271
272    /**
273     * @deprecated since 9.1, use policy and filter contributions instead
274     */
275    @Deprecated
276    protected void registerVersioningRule(VersioningRuleDescriptor contrib) {
277        versioningRulesRegistry.addContribution(contrib);
278        log.info("Registered versioning rule: " + contrib.getTypeName());
279        recompute();
280    }
281
282    /**
283     * @deprecated since 9.1, use policy and filter contributions instead
284     */
285    @Deprecated
286    protected void unregisterVersioningRule(VersioningRuleDescriptor contrib) {
287        versioningRulesRegistry.removeContribution(contrib);
288        log.info("Unregistered versioning rule: " + contrib.getTypeName());
289        recompute();
290    }
291
292    /**
293     * @deprecated since 9.1, use policy and filter contributions instead
294     */
295    @Deprecated
296    protected void registerDefaultVersioningRule(DefaultVersioningRuleDescriptor contrib) {
297        // could use a linked set instead, but given the size a linked list is enough
298        defaultVersioningRuleList.add(contrib);
299        recompute();
300    }
301
302    /**
303     * @deprecated since 9.1, use policy and filter contributions instead
304     */
305    @Deprecated
306    protected void unregisterDefaultVersioningRule(DefaultVersioningRuleDescriptor contrib) {
307        defaultVersioningRuleList.remove(contrib);
308        recompute();
309    }
310
311    protected void registerVersioningPolicy(VersioningPolicyDescriptor contrib) {
312        versioningPoliciesRegistry.addContribution(contrib);
313        log.info("Registered versioning policy: " + contrib.getId());
314        recompute();
315    }
316
317    protected void unregisterVersioningPolicy(VersioningPolicyDescriptor contrib) {
318        versioningPoliciesRegistry.removeContribution(contrib);
319        log.info("Unregistered versioning policy: " + contrib.getId());
320        recompute();
321    }
322
323    protected void registerVersioningFilter(VersioningFilterDescriptor contrib) {
324        versioningFiltersRegistry.addContribution(contrib);
325        log.info("Registered versioning filter: " + contrib.getId());
326        recompute();
327    }
328
329    protected void unregisterVersioningFilter(VersioningFilterDescriptor contrib) {
330        versioningFiltersRegistry.removeContribution(contrib);
331        log.info("Unregistered versioning filter: " + contrib.getId());
332        recompute();
333    }
334
335    protected void registerVersioningRestriction(VersioningRestrictionDescriptor contrib) {
336        versioningRestrictionsRegistry.addContribution(contrib);
337        log.info("Registered versioning restriction: " + contrib.getType());
338        recompute();
339    }
340
341    protected void unregisterVersioningRestriction(VersioningRestrictionDescriptor contrib) {
342        versioningRestrictionsRegistry.removeContribution(contrib);
343        log.info("Unregistered versioning restriction: " + contrib.getType());
344        recompute();
345    }
346
347    protected void recompute() {
348        VersioningService versioningService = STANDARD_VERSIONING_SERVICE;
349        for (VersioningService vs : versioningServices.values()) {
350            versioningService = vs;
351        }
352        if (versioningService instanceof ExtendableVersioningService) {
353            ExtendableVersioningService evs = (ExtendableVersioningService) versioningService;
354            evs.setVersioningPolicies(getVersioningPolicies());
355            evs.setVersioningFilters(getVersioningFilters());
356            evs.setVersioningRestrictions(getVersioningRestrictions());
357            evs.setVersioningRules(getVersioningRules());
358            evs.setDefaultVersioningRule(getDefaultVersioningRule());
359        }
360        this.service = versioningService;
361    }
362
363    /**
364     * @deprecated since 9.1, use policy and filter contributions instead
365     */
366    @Deprecated
367    protected Map<String, VersioningRuleDescriptor> getVersioningRules() {
368        return versioningRulesRegistry.getVersioningRuleDescriptors();
369    }
370
371    /**
372     * @deprecated since 9.1, use policy and filter contributions instead
373     */
374    @Deprecated
375    protected DefaultVersioningRuleDescriptor getDefaultVersioningRule() {
376        return defaultVersioningRuleList.peekLast();
377    }
378
379    protected Map<String, VersioningPolicyDescriptor> getVersioningPolicies() {
380        return versioningPoliciesRegistry.getVersioningPolicyDescriptors();
381    }
382
383    protected Map<String, VersioningFilterDescriptor> getVersioningFilters() {
384        return versioningFiltersRegistry.getVersioningFilterDescriptors();
385    }
386
387    protected Map<String, VersioningRestrictionDescriptor> getVersioningRestrictions() {
388        return versioningRestrictionsRegistry.getVersioningRestrictionDescriptors();
389    }
390
391    @Override
392    public String getVersionLabel(DocumentModel doc) {
393        return service.getVersionLabel(doc);
394    }
395
396    @Override
397    public void doPostCreate(Document doc, Map<String, Serializable> options) {
398        service.doPostCreate(doc, options);
399    }
400
401    @Override
402    public List<VersioningOption> getSaveOptions(DocumentModel docModel) {
403        return service.getSaveOptions(docModel);
404    }
405
406    @Override
407    public boolean isPreSaveDoingCheckOut(Document doc, boolean isDirty, VersioningOption option,
408            Map<String, Serializable> options) {
409        return service.isPreSaveDoingCheckOut(doc, isDirty, option, options);
410    }
411
412    @Override
413    public VersioningOption doPreSave(CoreSession session, Document doc, boolean isDirty, VersioningOption option,
414            String checkinComment, Map<String, Serializable> options) {
415        return service.doPreSave(session, doc, isDirty, option, checkinComment, options);
416    }
417
418    @Override
419    public boolean isPostSaveDoingCheckIn(Document doc, VersioningOption option, Map<String, Serializable> options) {
420        return service.isPostSaveDoingCheckIn(doc, option, options);
421    }
422
423    @Override
424    public Document doPostSave(CoreSession session, Document doc, VersioningOption option, String checkinComment,
425            Map<String, Serializable> options) {
426        return service.doPostSave(session, doc, option, checkinComment, options);
427    }
428
429    @Override
430    public Document doCheckIn(Document doc, VersioningOption option, String checkinComment) {
431        return service.doCheckIn(doc, option, checkinComment);
432    }
433
434    @Override
435    public void doCheckOut(Document doc) {
436        service.doCheckOut(doc);
437    }
438
439    @Override
440    public void doAutomaticVersioning(DocumentModel previousDocument, DocumentModel currentDocument, boolean before) {
441        service.doAutomaticVersioning(previousDocument, currentDocument, before);
442    }
443
444}