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