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