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 doc.setPropertyValue(MINOR_VERSION, Long.valueOf(getMinor(doc) + 1)); 130 } 131 132 protected void incrementByOption(Document doc, VersioningOption option) { 133 try { 134 if (option == MAJOR) { 135 incrementMajor(doc); 136 } else if (option == MINOR) { 137 incrementMinor(doc); 138 } 139 // else nothing 140 } catch (PropertyNotFoundException e) { 141 // ignore 142 } 143 } 144 145 @Override 146 public void doPostCreate(Document doc, Map<String, Serializable> options) { 147 if (doc.isVersion() || doc.isProxy()) { 148 return; 149 } 150 setInitialVersion(doc); 151 } 152 153 /** 154 * Sets the initial version on a document. Can be overridden. 155 */ 156 protected void setInitialVersion(Document doc) { 157 InitialStateDescriptor initialState = null; 158 if (versioningRules != null) { 159 VersioningRuleDescriptor versionRule = versioningRules.get(doc.getType().getName()); 160 if (versionRule != null) { 161 initialState = versionRule.getInitialState(); 162 } 163 } 164 if (initialState == null && defaultVersioningRule != null) { 165 initialState = defaultVersioningRule.getInitialState(); 166 } 167 if (initialState != null) { 168 int initialMajor = initialState.getMajor(); 169 int initialMinor = initialState.getMinor(); 170 setVersion(doc, initialMajor, initialMinor); 171 return; 172 } 173 setVersion(doc, 0, 0); 174 } 175 176 @Override 177 public List<VersioningOption> getSaveOptions(DocumentModel docModel) { 178 boolean versionable = docModel.isVersionable(); 179 String lifecycleState = docModel.getCoreSession().getCurrentLifeCycleState(docModel.getRef()); 180 String type = docModel.getType(); 181 return getSaveOptions(versionable, lifecycleState, type); 182 } 183 184 protected List<VersioningOption> getSaveOptions(Document doc) { 185 boolean versionable = doc.getType().getFacets().contains(FacetNames.VERSIONABLE); 186 String lifecycleState; 187 try { 188 lifecycleState = doc.getLifeCycleState(); 189 } catch (LifeCycleException e) { 190 lifecycleState = null; 191 } 192 String type = doc.getType().getName(); 193 return getSaveOptions(versionable, lifecycleState, type); 194 } 195 196 protected List<VersioningOption> getSaveOptions(boolean versionable, String lifecycleState, String type) { 197 if (!versionable) { 198 return Arrays.asList(NONE); 199 } 200 if (lifecycleState == null) { 201 return Arrays.asList(NONE); 202 } 203 SaveOptionsDescriptor option = null; 204 if (versioningRules != null) { 205 VersioningRuleDescriptor saveOption = versioningRules.get(type); 206 if (saveOption != null) { 207 option = saveOption.getOptions().get(lifecycleState); 208 if (option == null) { 209 // try on any life cycle state 210 option = saveOption.getOptions().get("*"); 211 } 212 } 213 } 214 if (option == null && defaultVersioningRule != null) { 215 option = defaultVersioningRule.getOptions().get(lifecycleState); 216 if (option == null) { 217 // try on any life cycle state 218 option = defaultVersioningRule.getOptions().get("*"); 219 } 220 } 221 if (option != null) { 222 return option.getVersioningOptionList(); 223 } 224 if (PROJECT_STATE.equals(lifecycleState) || APPROVED_STATE.equals(lifecycleState) 225 || OBSOLETE_STATE.equals(lifecycleState)) { 226 return Arrays.asList(NONE, MINOR, MAJOR); 227 } 228 if (FILE_TYPE.equals(type) || NOTE_TYPE.equals(type)) { 229 return Arrays.asList(NONE, MINOR, MAJOR); 230 } 231 return Arrays.asList(NONE); 232 } 233 234 protected VersioningOption validateOption(Document doc, VersioningOption option) { 235 List<VersioningOption> options = getSaveOptions(doc); 236 if (!options.contains(option)) { 237 option = options.isEmpty() ? NONE : options.get(0); 238 } 239 return option; 240 } 241 242 @Override 243 public boolean isPreSaveDoingCheckOut(Document doc, boolean isDirty, VersioningOption option, 244 Map<String, Serializable> options) { 245 boolean disableAutoCheckOut = Boolean.TRUE.equals(options.get(VersioningService.DISABLE_AUTO_CHECKOUT)); 246 return !doc.isCheckedOut() && isDirty && !disableAutoCheckOut; 247 } 248 249 @Override 250 public VersioningOption doPreSave(Document doc, boolean isDirty, VersioningOption option, String checkinComment, 251 Map<String, Serializable> options) { 252 option = validateOption(doc, option); 253 if (isPreSaveDoingCheckOut(doc, isDirty, option, options)) { 254 doCheckOut(doc); 255 followTransitionByOption(doc, option); 256 } 257 // transition follow shouldn't change what postSave options will be 258 return option; 259 } 260 261 protected void followTransitionByOption(Document doc, VersioningOption option) { 262 String lifecycleState = doc.getLifeCycleState(); 263 if (APPROVED_STATE.equals(lifecycleState) || OBSOLETE_STATE.equals(lifecycleState)) { 264 doc.followTransition(BACK_TO_PROJECT_TRANSITION); 265 } 266 } 267 268 @Override 269 public boolean isPostSaveDoingCheckIn(Document doc, VersioningOption option, Map<String, Serializable> options) { 270 // option = validateOption(doc, option); // validated before 271 return doc.isCheckedOut() && option != NONE; 272 } 273 274 @Override 275 public Document doPostSave(Document doc, VersioningOption option, String checkinComment, 276 Map<String, Serializable> options) { 277 if (isPostSaveDoingCheckIn(doc, option, options)) { 278 incrementByOption(doc, option); 279 return doc.checkIn(null, checkinComment); // auto-label 280 } 281 return null; 282 } 283 284 @Override 285 public Document doCheckIn(Document doc, VersioningOption option, String checkinComment) { 286 if (option != VersioningOption.NONE) { 287 incrementByOption(doc, option == MAJOR ? MAJOR : MINOR); 288 } 289 return doc.checkIn(null, checkinComment); // auto-label 290 } 291 292 @Override 293 public void doCheckOut(Document doc) { 294 doc.checkOut(); 295 // set version number to that of the last version 296 try { 297 Document last = doc.getLastVersion(); 298 if (last != null) { 299 setVersion(doc, getMajor(last), getMinor(last)); 300 } 301 } catch (PropertyNotFoundException e) { 302 // ignore 303 } 304 } 305 306 @Override 307 public Map<String, VersioningRuleDescriptor> getVersioningRules() { 308 return versioningRules; 309 } 310 311 @Override 312 public void setVersioningRules(Map<String, VersioningRuleDescriptor> versioningRules) { 313 this.versioningRules = versioningRules; 314 } 315 316 @Override 317 public void setDefaultVersioningRule(DefaultVersioningRuleDescriptor defaultVersioningRule) { 318 this.defaultVersioningRule = defaultVersioningRule; 319 } 320 321}