001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Florent Guillaume
011 *     Laurent Doguin
012 */
013package org.nuxeo.ecm.core.versioning;
014
015import java.io.Serializable;
016import java.util.ArrayDeque;
017import java.util.Deque;
018import java.util.LinkedHashMap;
019import java.util.List;
020import java.util.Map;
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024import org.nuxeo.ecm.core.api.DocumentModel;
025import org.nuxeo.ecm.core.api.VersioningOption;
026import org.nuxeo.ecm.core.model.Document;
027import org.nuxeo.runtime.model.ComponentContext;
028import org.nuxeo.runtime.model.ComponentInstance;
029import org.nuxeo.runtime.model.DefaultComponent;
030import org.nuxeo.runtime.model.SimpleContributionRegistry;
031
032/**
033 * Versioning service component and implementation.
034 */
035public class VersioningComponent extends DefaultComponent implements VersioningService {
036
037    private static final Log log = LogFactory.getLog(VersioningComponent.class);
038
039    public static final String VERSIONING_SERVICE_XP = "versioningService";
040
041    public static final String VERSIONING_RULE_XP = "versioningRules";
042
043    protected static final StandardVersioningService STANDARD_VERSIONING_SERVICE = new StandardVersioningService();
044
045    protected Map<VersioningServiceDescriptor, VersioningService> versioningServices = new LinkedHashMap<>();
046
047    protected VersioningRuleRegistry versioningRulesRegistry = new VersioningRuleRegistry();
048
049    protected static class VersioningRuleRegistry extends SimpleContributionRegistry<VersioningRuleDescriptor> {
050
051        @Override
052        public String getContributionId(VersioningRuleDescriptor contrib) {
053            return contrib.getTypeName();
054        }
055
056        @Override
057        public VersioningRuleDescriptor clone(VersioningRuleDescriptor orig) {
058            return new VersioningRuleDescriptor(orig);
059        }
060
061        @Override
062        public void merge(VersioningRuleDescriptor src, VersioningRuleDescriptor dst) {
063            dst.merge(src);
064        }
065
066        @Override
067        public boolean isSupportingMerge() {
068            return true;
069        }
070
071        @Override
072        public void contributionUpdated(String id, VersioningRuleDescriptor contrib,
073                VersioningRuleDescriptor newOrigContrib) {
074            if (contrib.isEnabled()) {
075                currentContribs.put(id, contrib);
076            } else {
077                currentContribs.remove(id);
078            }
079        }
080
081        public void clear() {
082            currentContribs.clear();
083        }
084
085        public Map<String, VersioningRuleDescriptor> getVersioningRuleDescriptors() {
086            return currentContribs;
087        }
088    }
089
090    protected Deque<DefaultVersioningRuleDescriptor> defaultVersioningRuleList = new ArrayDeque<>();
091
092    // public for tests
093    public VersioningService service = STANDARD_VERSIONING_SERVICE;
094
095    protected ComponentContext context;
096
097    @Override
098    public void activate(ComponentContext context) {
099        this.context = context;
100    }
101
102    @Override
103    public void deactivate(ComponentContext context) {
104        this.context = null;
105    }
106
107    @Override
108    public void registerContribution(Object contrib, String point, ComponentInstance contributor) {
109        if (VERSIONING_SERVICE_XP.equals(point)) {
110            registerVersioningService((VersioningServiceDescriptor) contrib);
111        } else if (VERSIONING_RULE_XP.equals(point)) {
112            if (contrib instanceof VersioningRuleDescriptor) {
113                registerVersioningRule((VersioningRuleDescriptor) contrib);
114            } else if (contrib instanceof DefaultVersioningRuleDescriptor) {
115                registerDefaultVersioningRule((DefaultVersioningRuleDescriptor) contrib);
116            } else {
117                throw new RuntimeException("Unknown contribution to " + point + ": " + contrib.getClass());
118            }
119        } else {
120            throw new RuntimeException("Unknown extension point: " + point);
121        }
122    }
123
124    @Override
125    public void unregisterContribution(Object contrib, String point, ComponentInstance contributor) {
126        if (VERSIONING_SERVICE_XP.equals(point)) {
127            unregisterVersioningService((VersioningServiceDescriptor) contrib);
128        } else if (VERSIONING_RULE_XP.equals(point)) {
129            if (contrib instanceof VersioningRuleDescriptor) {
130                unregisterVersioningRule((VersioningRuleDescriptor) contrib);
131            } else if (contrib instanceof DefaultVersioningRuleDescriptor) {
132                unregisterDefaultVersioningRule((DefaultVersioningRuleDescriptor) contrib);
133            }
134        }
135    }
136
137    protected void registerVersioningService(VersioningServiceDescriptor contrib) {
138        String klass = contrib.className;
139        try {
140            VersioningService vs = (VersioningService) context.getRuntimeContext().loadClass(klass).newInstance();
141            versioningServices.put(contrib, vs);
142        } catch (ReflectiveOperationException e) {
143            throw new RuntimeException("Failed to instantiate: " + klass, e);
144        }
145        log.info("Registered versioning service: " + klass);
146        recompute();
147    }
148
149    protected void unregisterVersioningService(VersioningServiceDescriptor contrib) {
150        versioningServices.remove(contrib);
151        log.info("Unregistered versioning service: " + contrib.className);
152        recompute();
153    }
154
155    protected void registerVersioningRule(VersioningRuleDescriptor contrib) {
156        versioningRulesRegistry.addContribution(contrib);
157        log.info("Registered versioning rule: " + contrib.getTypeName());
158        recompute();
159    }
160
161    protected void unregisterVersioningRule(VersioningRuleDescriptor contrib) {
162        versioningRulesRegistry.removeContribution(contrib);
163        log.info("Unregistered versioning rule: " + contrib.getTypeName());
164        recompute();
165    }
166
167    protected void registerDefaultVersioningRule(DefaultVersioningRuleDescriptor contrib) {
168        // could use a linked set instead, but given the size a linked list is enough
169        defaultVersioningRuleList.add(contrib);
170        recompute();
171    }
172
173    protected void unregisterDefaultVersioningRule(DefaultVersioningRuleDescriptor contrib) {
174        defaultVersioningRuleList.remove(contrib);
175        recompute();
176    }
177
178    protected void recompute() {
179        VersioningService versioningService = STANDARD_VERSIONING_SERVICE;
180        for (VersioningService vs : versioningServices.values()) {
181            versioningService = vs;
182        }
183        if (versioningService instanceof ExtendableVersioningService) {
184            ExtendableVersioningService vs = (ExtendableVersioningService) versioningService;
185            vs.setVersioningRules(getVersioningRules());
186            vs.setDefaultVersioningRule(getDefaultVersioningRule());
187        }
188        this.service = versioningService;
189    }
190
191    protected Map<String, VersioningRuleDescriptor> getVersioningRules() {
192        return versioningRulesRegistry.getVersioningRuleDescriptors();
193    }
194
195    protected DefaultVersioningRuleDescriptor getDefaultVersioningRule() {
196        return defaultVersioningRuleList.peekLast();
197    }
198
199    @Override
200    public String getVersionLabel(DocumentModel doc) {
201        return service.getVersionLabel(doc);
202    }
203
204    @Override
205    public void doPostCreate(Document doc, Map<String, Serializable> options) {
206        service.doPostCreate(doc, options);
207    }
208
209    @Override
210    public List<VersioningOption> getSaveOptions(DocumentModel docModel) {
211        return service.getSaveOptions(docModel);
212    }
213
214    @Override
215    public boolean isPreSaveDoingCheckOut(Document doc, boolean isDirty, VersioningOption option,
216            Map<String, Serializable> options) {
217        return service.isPreSaveDoingCheckOut(doc, isDirty, option, options);
218    }
219
220    @Override
221    public VersioningOption doPreSave(Document doc, boolean isDirty, VersioningOption option, String checkinComment,
222            Map<String, Serializable> options) {
223        return service.doPreSave(doc, isDirty, option, checkinComment, options);
224    }
225
226    @Override
227    public boolean isPostSaveDoingCheckIn(Document doc, VersioningOption option, Map<String, Serializable> options) {
228        return service.isPostSaveDoingCheckIn(doc, option, options);
229    }
230
231    @Override
232    public Document doPostSave(Document doc, VersioningOption option, String checkinComment,
233            Map<String, Serializable> options) {
234        return service.doPostSave(doc, option, checkinComment, options);
235    }
236
237    @Override
238    public Document doCheckIn(Document doc, VersioningOption option, String checkinComment) {
239        return service.doCheckIn(doc, option, checkinComment);
240    }
241
242    @Override
243    public void doCheckOut(Document doc) {
244        service.doCheckOut(doc);
245    }
246}