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