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