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 = null;
169
170    protected ComponentContext context;
171
172    @Override
173    public void activate(ComponentContext context) {
174        this.context = context;
175        this.service = STANDARD_VERSIONING_SERVICE;
176    }
177
178    @Override
179    public void deactivate(ComponentContext context) {
180        this.context = null;
181        this.service = null;
182    }
183
184    @Override
185    public void registerContribution(Object contrib, String point, ComponentInstance contributor) {
186        switch (point) {
187        case VERSIONING_SERVICE_XP:
188            registerVersioningService((VersioningServiceDescriptor) contrib);
189            break;
190        case VERSIONING_RULE_XP:
191            if (contrib instanceof VersioningRuleDescriptor) {
192                VersioningRuleDescriptor rule = (VersioningRuleDescriptor) contrib;
193                registerVersioningRule(rule);
194                String message = String.format(
195                        "Versioning rule for '%s' on component %s should now be contributed to extension points '%s', "
196                                + "'%s' and '%s': a compatibility registration was performed but it may not be accurate.",
197                        (rule).getTypeName(), contributor.getName(), VERSIONING_POLICY_XP, VERSIONING_FILTER_XP,
198                        VERSIONING_RESTRICTION_XP);
199                DeprecationLogger.log(message, "9.1");
200                Framework.getRuntime().getWarnings().add(message);
201            } else if (contrib instanceof DefaultVersioningRuleDescriptor) {
202                registerDefaultVersioningRule((DefaultVersioningRuleDescriptor) contrib);
203                String message = String.format("Default versioning rule on component %s should now be contributed to "
204                        + "extension points '%s' and '%s': a compatibility registration was performed but it may not be "
205                        + "accurate.", contributor.getName(), VERSIONING_POLICY_XP, VERSIONING_RESTRICTION_XP);
206                DeprecationLogger.log(message, "9.1");
207                Framework.getRuntime().getWarnings().add(message);
208            } else {
209                throw new RuntimeException("Unknown contribution to " + point + ": " + contrib.getClass());
210            }
211            break;
212        case VERSIONING_POLICY_XP:
213            registerVersioningPolicy((VersioningPolicyDescriptor) contrib);
214            break;
215        case VERSIONING_FILTER_XP:
216            registerVersioningFilter((VersioningFilterDescriptor) contrib);
217            break;
218        case VERSIONING_RESTRICTION_XP:
219            registerVersioningRestriction((VersioningRestrictionDescriptor) contrib);
220            break;
221        default:
222            throw new RuntimeException("Unknown extension point: " + point);
223        }
224    }
225
226    @Override
227    public void unregisterContribution(Object contrib, String point, ComponentInstance contributor) {
228        switch (point) {
229        case VERSIONING_SERVICE_XP:
230            unregisterVersioningService((VersioningServiceDescriptor) contrib);
231            break;
232        case VERSIONING_RULE_XP:
233            if (contrib instanceof VersioningRuleDescriptor) {
234                unregisterVersioningRule((VersioningRuleDescriptor) contrib);
235            } else if (contrib instanceof DefaultVersioningRuleDescriptor) {
236                unregisterDefaultVersioningRule((DefaultVersioningRuleDescriptor) contrib);
237            }
238            break;
239        case VERSIONING_POLICY_XP:
240            unregisterVersioningPolicy((VersioningPolicyDescriptor) contrib);
241            break;
242        case VERSIONING_FILTER_XP:
243            unregisterVersioningFilter((VersioningFilterDescriptor) contrib);
244            break;
245        case VERSIONING_RESTRICTION_XP:
246            unregisterVersioningRestriction((VersioningRestrictionDescriptor) contrib);
247            break;
248        default:
249            break;
250        }
251    }
252
253    protected void registerVersioningService(VersioningServiceDescriptor contrib) {
254        String klass = contrib.className;
255        try {
256            VersioningService vs = (VersioningService) context.getRuntimeContext().loadClass(klass).newInstance();
257            versioningServices.put(contrib, vs);
258        } catch (ReflectiveOperationException e) {
259            throw new RuntimeException("Failed to instantiate: " + klass, e);
260        }
261        log.info("Registered versioning service: " + klass);
262        recompute();
263    }
264
265    protected void unregisterVersioningService(VersioningServiceDescriptor contrib) {
266        versioningServices.remove(contrib);
267        log.info("Unregistered versioning service: " + contrib.className);
268        recompute();
269    }
270
271    /**
272     * @deprecated since 9.1, use policy and filter contributions instead
273     */
274    @Deprecated
275    protected void registerVersioningRule(VersioningRuleDescriptor contrib) {
276        versioningRulesRegistry.addContribution(contrib);
277        log.info("Registered versioning rule: " + contrib.getTypeName());
278        recompute();
279    }
280
281    /**
282     * @deprecated since 9.1, use policy and filter contributions instead
283     */
284    @Deprecated
285    protected void unregisterVersioningRule(VersioningRuleDescriptor contrib) {
286        versioningRulesRegistry.removeContribution(contrib);
287        log.info("Unregistered versioning rule: " + contrib.getTypeName());
288        recompute();
289    }
290
291    /**
292     * @deprecated since 9.1, use policy and filter contributions instead
293     */
294    @Deprecated
295    protected void registerDefaultVersioningRule(DefaultVersioningRuleDescriptor contrib) {
296        // could use a linked set instead, but given the size a linked list is enough
297        defaultVersioningRuleList.add(contrib);
298        recompute();
299    }
300
301    /**
302     * @deprecated since 9.1, use policy and filter contributions instead
303     */
304    @Deprecated
305    protected void unregisterDefaultVersioningRule(DefaultVersioningRuleDescriptor contrib) {
306        defaultVersioningRuleList.remove(contrib);
307        recompute();
308    }
309
310    protected void registerVersioningPolicy(VersioningPolicyDescriptor contrib) {
311        versioningPoliciesRegistry.addContribution(contrib);
312        log.info("Registered versioning policy: " + contrib.getId());
313        recompute();
314    }
315
316    protected void unregisterVersioningPolicy(VersioningPolicyDescriptor contrib) {
317        versioningPoliciesRegistry.removeContribution(contrib);
318        log.info("Unregistered versioning policy: " + contrib.getId());
319        recompute();
320    }
321
322    protected void registerVersioningFilter(VersioningFilterDescriptor contrib) {
323        versioningFiltersRegistry.addContribution(contrib);
324        log.info("Registered versioning filter: " + contrib.getId());
325        recompute();
326    }
327
328    protected void unregisterVersioningFilter(VersioningFilterDescriptor contrib) {
329        versioningFiltersRegistry.removeContribution(contrib);
330        log.info("Unregistered versioning filter: " + contrib.getId());
331        recompute();
332    }
333
334    protected void registerVersioningRestriction(VersioningRestrictionDescriptor contrib) {
335        versioningRestrictionsRegistry.addContribution(contrib);
336        log.info("Registered versioning restriction: " + contrib.getType());
337        recompute();
338    }
339
340    protected void unregisterVersioningRestriction(VersioningRestrictionDescriptor contrib) {
341        versioningRestrictionsRegistry.removeContribution(contrib);
342        log.info("Unregistered versioning restriction: " + contrib.getType());
343        recompute();
344    }
345
346    protected void recompute() {
347        VersioningService versioningService = STANDARD_VERSIONING_SERVICE;
348        for (VersioningService vs : versioningServices.values()) {
349            versioningService = vs;
350        }
351        if (versioningService instanceof ExtendableVersioningService) {
352            ExtendableVersioningService evs = (ExtendableVersioningService) versioningService;
353            evs.setVersioningPolicies(getVersioningPolicies());
354            evs.setVersioningFilters(getVersioningFilters());
355            evs.setVersioningRestrictions(getVersioningRestrictions());
356            evs.setVersioningRules(getVersioningRules());
357            evs.setDefaultVersioningRule(getDefaultVersioningRule());
358        }
359        this.service = versioningService;
360    }
361
362    /**
363     * @deprecated since 9.1, use policy and filter contributions instead
364     */
365    @Deprecated
366    protected Map<String, VersioningRuleDescriptor> getVersioningRules() {
367        return versioningRulesRegistry.getVersioningRuleDescriptors();
368    }
369
370    /**
371     * @deprecated since 9.1, use policy and filter contributions instead
372     */
373    @Deprecated
374    protected DefaultVersioningRuleDescriptor getDefaultVersioningRule() {
375        return defaultVersioningRuleList.peekLast();
376    }
377
378    protected Map<String, VersioningPolicyDescriptor> getVersioningPolicies() {
379        return versioningPoliciesRegistry.getVersioningPolicyDescriptors();
380    }
381
382    protected Map<String, VersioningFilterDescriptor> getVersioningFilters() {
383        return versioningFiltersRegistry.getVersioningFilterDescriptors();
384    }
385
386    protected Map<String, VersioningRestrictionDescriptor> getVersioningRestrictions() {
387        return versioningRestrictionsRegistry.getVersioningRestrictionDescriptors();
388    }
389
390    @Override
391    public String getVersionLabel(DocumentModel doc) {
392        return service.getVersionLabel(doc);
393    }
394
395    @Override
396    public void doPostCreate(Document doc, Map<String, Serializable> options) {
397        service.doPostCreate(doc, options);
398    }
399
400    @Override
401    public List<VersioningOption> getSaveOptions(DocumentModel docModel) {
402        return service.getSaveOptions(docModel);
403    }
404
405    @Override
406    public boolean isPreSaveDoingCheckOut(Document doc, boolean isDirty, VersioningOption option,
407            Map<String, Serializable> options) {
408        return service.isPreSaveDoingCheckOut(doc, isDirty, option, options);
409    }
410
411    @Override
412    public VersioningOption doPreSave(Document doc, boolean isDirty, VersioningOption option, String checkinComment,
413            Map<String, Serializable> options) {
414        return service.doPreSave(doc, isDirty, option, checkinComment, options);
415    }
416
417    @Override
418    public boolean isPostSaveDoingCheckIn(Document doc, VersioningOption option, Map<String, Serializable> options) {
419        return service.isPostSaveDoingCheckIn(doc, option, options);
420    }
421
422    @Override
423    public Document doPostSave(Document doc, VersioningOption option, String checkinComment,
424            Map<String, Serializable> options) {
425        return service.doPostSave(doc, option, checkinComment, options);
426    }
427
428    @Override
429    public Document doCheckIn(Document doc, VersioningOption option, String checkinComment) {
430        return service.doCheckIn(doc, option, checkinComment);
431    }
432
433    @Override
434    public void doCheckOut(Document doc) {
435        service.doCheckOut(doc);
436    }
437
438    @Override
439    public void doAutomaticVersioning(DocumentModel previousDocument, DocumentModel currentDocument, boolean before) {
440        service.doAutomaticVersioning(previousDocument, currentDocument, before);
441    }
442
443}