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 static org.nuxeo.ecm.core.api.VersioningOption.MAJOR; 016import static org.nuxeo.ecm.core.api.VersioningOption.MINOR; 017import static org.nuxeo.ecm.core.api.VersioningOption.NONE; 018 019import java.io.Serializable; 020import java.util.Arrays; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.nuxeo.ecm.core.api.DocumentModel; 027import org.nuxeo.ecm.core.api.LifeCycleException; 028import org.nuxeo.ecm.core.api.VersioningOption; 029import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 030import org.nuxeo.ecm.core.model.Document; 031import org.nuxeo.ecm.core.schema.FacetNames; 032 033/** 034 * Implementation of the versioning service that follows standard checkout / checkin semantics. 035 */ 036public class StandardVersioningService implements ExtendableVersioningService { 037 038 private static final Log log = LogFactory.getLog(StandardVersioningService.class); 039 040 public static final String FILE_TYPE = "File"; 041 042 public static final String NOTE_TYPE = "Note"; 043 044 public static final String PROJECT_STATE = "project"; 045 046 public static final String APPROVED_STATE = "approved"; 047 048 public static final String OBSOLETE_STATE = "obsolete"; 049 050 public static final String BACK_TO_PROJECT_TRANSITION = "backToProject"; 051 052 protected static final String AUTO_CHECKED_OUT = "AUTO_CHECKED_OUT"; 053 054 /** Key for major version in Document API. */ 055 protected static final String MAJOR_VERSION = "ecm:majorVersion"; 056 057 /** Key for minor version in Document API. */ 058 protected static final String MINOR_VERSION = "ecm:minorVersion"; 059 060 private Map<String, VersioningRuleDescriptor> versioningRules; 061 062 private DefaultVersioningRuleDescriptor defaultVersioningRule; 063 064 @Override 065 public String getVersionLabel(DocumentModel docModel) { 066 String label; 067 try { 068 label = getMajor(docModel) + "." + getMinor(docModel); 069 if (docModel.isCheckedOut() && !"0.0".equals(label)) { 070 label += "+"; 071 } 072 } catch (PropertyNotFoundException e) { 073 label = ""; 074 } 075 return label; 076 } 077 078 protected long getMajor(DocumentModel docModel) { 079 return getVersion(docModel, VersioningService.MAJOR_VERSION_PROP); 080 } 081 082 protected long getMinor(DocumentModel docModel) { 083 return getVersion(docModel, VersioningService.MINOR_VERSION_PROP); 084 } 085 086 protected long getVersion(DocumentModel docModel, String prop) { 087 Object propVal = docModel.getPropertyValue(prop); 088 if (propVal == null || !(propVal instanceof Long)) { 089 return 0; 090 } else { 091 return ((Long) propVal).longValue(); 092 } 093 } 094 095 protected long getMajor(Document doc) { 096 return getVersion(doc, MAJOR_VERSION); 097 } 098 099 protected long getMinor(Document doc) { 100 return getVersion(doc, MINOR_VERSION); 101 } 102 103 protected long getVersion(Document doc, String prop) { 104 Object propVal = doc.getPropertyValue(prop); 105 if (propVal == null || !(propVal instanceof Long)) { 106 return 0; 107 } else { 108 return ((Long) propVal).longValue(); 109 } 110 } 111 112 protected void setVersion(Document doc, long major, long minor) { 113 doc.setPropertyValue(MAJOR_VERSION, Long.valueOf(major)); 114 doc.setPropertyValue(MINOR_VERSION, Long.valueOf(minor)); 115 } 116 117 protected void incrementMajor(Document doc) { 118 setVersion(doc, getMajor(doc) + 1, 0); 119 } 120 121 protected void incrementMinor(Document doc) { 122 doc.setPropertyValue(MINOR_VERSION, Long.valueOf(getMinor(doc) + 1)); 123 } 124 125 protected void incrementByOption(Document doc, VersioningOption option) { 126 try { 127 if (option == MAJOR) { 128 incrementMajor(doc); 129 } else if (option == MINOR) { 130 incrementMinor(doc); 131 } 132 // else nothing 133 } catch (PropertyNotFoundException e) { 134 // ignore 135 } 136 } 137 138 @Override 139 public void doPostCreate(Document doc, Map<String, Serializable> options) { 140 if (doc.isVersion() || doc.isProxy()) { 141 return; 142 } 143 setInitialVersion(doc); 144 } 145 146 /** 147 * Sets the initial version on a document. Can be overridden. 148 */ 149 protected void setInitialVersion(Document doc) { 150 InitialStateDescriptor initialState = null; 151 if (versioningRules != null) { 152 VersioningRuleDescriptor versionRule = versioningRules.get(doc.getType().getName()); 153 if (versionRule != null) { 154 initialState = versionRule.getInitialState(); 155 } 156 } 157 if (initialState == null && defaultVersioningRule != null) { 158 initialState = defaultVersioningRule.getInitialState(); 159 } 160 if (initialState != null) { 161 int initialMajor = initialState.getMajor(); 162 int initialMinor = initialState.getMinor(); 163 setVersion(doc, initialMajor, initialMinor); 164 return; 165 } 166 setVersion(doc, 0, 0); 167 } 168 169 @Override 170 public List<VersioningOption> getSaveOptions(DocumentModel docModel) { 171 boolean versionable = docModel.isVersionable(); 172 String lifecycleState = docModel.getCoreSession().getCurrentLifeCycleState(docModel.getRef()); 173 String type = docModel.getType(); 174 return getSaveOptions(versionable, lifecycleState, type); 175 } 176 177 protected List<VersioningOption> getSaveOptions(Document doc) { 178 boolean versionable = doc.getType().getFacets().contains(FacetNames.VERSIONABLE); 179 String lifecycleState; 180 try { 181 lifecycleState = doc.getLifeCycleState(); 182 } catch (LifeCycleException e) { 183 lifecycleState = null; 184 } 185 String type = doc.getType().getName(); 186 return getSaveOptions(versionable, lifecycleState, type); 187 } 188 189 protected List<VersioningOption> getSaveOptions(boolean versionable, String lifecycleState, String type) { 190 if (!versionable) { 191 return Arrays.asList(NONE); 192 } 193 if (lifecycleState == null) { 194 return Arrays.asList(NONE); 195 } 196 SaveOptionsDescriptor option = null; 197 if (versioningRules != null) { 198 VersioningRuleDescriptor saveOption = versioningRules.get(type); 199 if (saveOption != null) { 200 option = saveOption.getOptions().get(lifecycleState); 201 if (option == null) { 202 // try on any life cycle state 203 option = saveOption.getOptions().get("*"); 204 } 205 } 206 } 207 if (option == null && defaultVersioningRule != null) { 208 option = defaultVersioningRule.getOptions().get(lifecycleState); 209 if (option == null) { 210 // try on any life cycle state 211 option = defaultVersioningRule.getOptions().get("*"); 212 } 213 } 214 if (option != null) { 215 return option.getVersioningOptionList(); 216 } 217 if (PROJECT_STATE.equals(lifecycleState) || APPROVED_STATE.equals(lifecycleState) 218 || OBSOLETE_STATE.equals(lifecycleState)) { 219 return Arrays.asList(NONE, MINOR, MAJOR); 220 } 221 if (FILE_TYPE.equals(type) || NOTE_TYPE.equals(type)) { 222 return Arrays.asList(NONE, MINOR, MAJOR); 223 } 224 return Arrays.asList(NONE); 225 } 226 227 protected VersioningOption validateOption(Document doc, VersioningOption option) { 228 List<VersioningOption> options = getSaveOptions(doc); 229 if (!options.contains(option)) { 230 option = options.isEmpty() ? NONE : options.get(0); 231 } 232 return option; 233 } 234 235 @Override 236 public boolean isPreSaveDoingCheckOut(Document doc, boolean isDirty, VersioningOption option, 237 Map<String, Serializable> options) { 238 boolean disableAutoCheckOut = Boolean.TRUE.equals(options.get(VersioningService.DISABLE_AUTO_CHECKOUT)); 239 return !doc.isCheckedOut() && isDirty && !disableAutoCheckOut; 240 } 241 242 @Override 243 public VersioningOption doPreSave(Document doc, boolean isDirty, VersioningOption option, String checkinComment, 244 Map<String, Serializable> options) { 245 option = validateOption(doc, option); 246 if (isPreSaveDoingCheckOut(doc, isDirty, option, options)) { 247 doCheckOut(doc); 248 followTransitionByOption(doc, option); 249 } 250 // transition follow shouldn't change what postSave options will be 251 return option; 252 } 253 254 protected void followTransitionByOption(Document doc, VersioningOption option) { 255 String lifecycleState = doc.getLifeCycleState(); 256 if (APPROVED_STATE.equals(lifecycleState) || OBSOLETE_STATE.equals(lifecycleState)) { 257 doc.followTransition(BACK_TO_PROJECT_TRANSITION); 258 } 259 } 260 261 @Override 262 public boolean isPostSaveDoingCheckIn(Document doc, VersioningOption option, Map<String, Serializable> options) { 263 // option = validateOption(doc, option); // validated before 264 return doc.isCheckedOut() && option != NONE; 265 } 266 267 @Override 268 public Document doPostSave(Document doc, VersioningOption option, String checkinComment, 269 Map<String, Serializable> options) { 270 if (isPostSaveDoingCheckIn(doc, option, options)) { 271 incrementByOption(doc, option); 272 return doc.checkIn(null, checkinComment); // auto-label 273 } 274 return null; 275 } 276 277 @Override 278 public Document doCheckIn(Document doc, VersioningOption option, String checkinComment) { 279 if (option != VersioningOption.NONE) { 280 incrementByOption(doc, option == MAJOR ? MAJOR : MINOR); 281 } 282 return doc.checkIn(null, checkinComment); // auto-label 283 } 284 285 @Override 286 public void doCheckOut(Document doc) { 287 doc.checkOut(); 288 // set version number to that of the last version 289 try { 290 Document last = doc.getLastVersion(); 291 if (last != null) { 292 setVersion(doc, getMajor(last), getMinor(last)); 293 } 294 } catch (PropertyNotFoundException e) { 295 // ignore 296 } 297 } 298 299 @Override 300 public Map<String, VersioningRuleDescriptor> getVersioningRules() { 301 return versioningRules; 302 } 303 304 @Override 305 public void setVersioningRules(Map<String, VersioningRuleDescriptor> versioningRules) { 306 this.versioningRules = versioningRules; 307 } 308 309 @Override 310 public void setDefaultVersioningRule(DefaultVersioningRuleDescriptor defaultVersioningRule) { 311 this.defaultVersioningRule = defaultVersioningRule; 312 } 313 314}