/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Florent Guillaume
* Laurent Doguin
*/
package org.eclipse.ecr.core.versioning;
import static org.eclipse.ecr.core.api.VersioningOption.MAJOR;
import static org.eclipse.ecr.core.api.VersioningOption.MINOR;
import static org.eclipse.ecr.core.api.VersioningOption.NONE;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.ecr.core.api.ClientException;
import org.eclipse.ecr.core.api.DocumentException;
import org.eclipse.ecr.core.api.DocumentModel;
import org.eclipse.ecr.core.api.VersioningOption;
import org.eclipse.ecr.core.api.model.PropertyNotFoundException;
import org.eclipse.ecr.core.lifecycle.LifeCycleException;
import org.eclipse.ecr.core.model.Document;
import org.eclipse.ecr.core.model.NoSuchPropertyException;
import org.eclipse.ecr.core.schema.FacetNames;
/**
* Implementation of the versioning service that follows standard checkout /
* checkin semantics.
*/
public class StandardVersioningService implements ExtendableVersioningService {
private static final Log log = LogFactory.getLog(StandardVersioningService.class);
public static final String FILE_TYPE = "File";
public static final String NOTE_TYPE = "Note";
public static final String PROJECT_STATE = "project";
public static final String APPROVED_STATE = "approved";
public static final String OBSOLETE_STATE = "obsolete";
public static final String BACK_TO_PROJECT_TRANSITION = "backToProject";
protected static final String AUTO_CHECKED_OUT = "AUTO_CHECKED_OUT";
/** Key for major version in Document API. */
protected static final String MAJOR_VERSION = "major_version";
/** Key for minor version in Document API. */
protected static final String MINOR_VERSION = "minor_version";
private Map<String, VersioningRuleDescriptor> versioningRules;
private DefaultVersioningRuleDescriptor defaultVersioningRule;
@Override
public String getVersionLabel(DocumentModel docModel) {
String label;
try {
label = getMajor(docModel) + "." + getMinor(docModel);
if (docModel.isCheckedOut() && !"0.0".equals(label)) {
label += "+";
}
} catch (PropertyNotFoundException e) {
label = "";
} catch (ClientException e) {
log.debug("No version label", e);
label = "";
}
return label;
}
protected long getMajor(DocumentModel docModel) throws ClientException {
return getVersion(docModel, VersioningService.MAJOR_VERSION_PROP);
}
protected long getMinor(DocumentModel docModel) throws ClientException {
return getVersion(docModel, VersioningService.MINOR_VERSION_PROP);
}
protected long getVersion(DocumentModel docModel, String prop)
throws ClientException {
Object propVal = docModel.getPropertyValue(prop);
if (propVal == null || !(propVal instanceof Long)) {
return 0;
} else {
return ((Long) propVal).longValue();
}
}
protected long getMajor(Document doc) throws DocumentException {
return getVersion(doc, MAJOR_VERSION);
}
protected long getMinor(Document doc) throws DocumentException {
return getVersion(doc, MINOR_VERSION);
}
protected long getVersion(Document doc, String prop)
throws DocumentException {
Object propVal = doc.getPropertyValue(prop);
if (propVal == null || !(propVal instanceof Long)) {
return 0;
} else {
return ((Long) propVal).longValue();
}
}
protected void setVersion(Document doc, long major, long minor)
throws DocumentException {
doc.setPropertyValue(MAJOR_VERSION, Long.valueOf(major));
doc.setPropertyValue(MINOR_VERSION, Long.valueOf(minor));
}
protected void incrementMajor(Document doc) throws DocumentException {
setVersion(doc, getMajor(doc) + 1, 0);
}
protected void incrementMinor(Document doc) throws DocumentException {
doc.setPropertyValue("minor_version", Long.valueOf(getMinor(doc) + 1));
}
protected void incrementByOption(Document doc, VersioningOption option)
throws DocumentException {
try {
if (option == MAJOR) {
incrementMajor(doc);
} else if (option == MINOR) {
incrementMinor(doc);
}
// else nothing
} catch (NoSuchPropertyException e) {
// ignore
}
}
@Override
public void doPostCreate(Document doc, Map<String, Serializable> options) {
if (doc.isVersion() || doc.isProxy()) {
return;
}
try {
setInitialVersion(doc);
} catch (DocumentException e) {
// ignore
}
}
/**
* Sets the initial version on a document. Can be overridden.
*/
protected void setInitialVersion(Document doc) throws DocumentException {
InitialStateDescriptor initialState = null;
if (versioningRules != null) {
VersioningRuleDescriptor versionRule = versioningRules.get(doc.getType().getName());
if (versionRule != null) {
initialState = versionRule.getInitialState();
}
}
if (initialState == null && defaultVersioningRule != null) {
initialState = defaultVersioningRule.getInitialState();
}
if (initialState != null) {
int initialMajor = initialState.getMajor();
int initialMinor = initialState.getMinor();
setVersion(doc, initialMajor, initialMinor);
return;
}
setVersion(doc, 0, 0);
}
@Override
public List<VersioningOption> getSaveOptions(DocumentModel docModel)
throws ClientException {
boolean versionable = docModel.isVersionable();
String lifecycleState;
try {
lifecycleState = docModel.getCoreSession().getCurrentLifeCycleState(
docModel.getRef());
} catch (ClientException e) {
lifecycleState = null;
}
String type = docModel.getType();
return getSaveOptions(versionable, lifecycleState, type);
}
protected List<VersioningOption> getSaveOptions(Document doc)
throws DocumentException {
boolean versionable = doc.getType().getFacets().contains(
FacetNames.VERSIONABLE);
String lifecycleState;
try {
lifecycleState = doc.getLifeCycleState();
} catch (LifeCycleException e) {
lifecycleState = null;
}
String type = doc.getType().getName();
return getSaveOptions(versionable, lifecycleState, type);
}
protected List<VersioningOption> getSaveOptions(boolean versionable,
String lifecycleState, String type) {
if (!versionable) {
return Arrays.asList(NONE);
}
if (lifecycleState == null) {
return Arrays.asList(NONE);
}
SaveOptionsDescriptor option = null;
if (versioningRules != null) {
VersioningRuleDescriptor saveOption = versioningRules.get(type);
if (saveOption != null) {
option = saveOption.getOptions().get(lifecycleState);
}
}
if (option == null && defaultVersioningRule != null) {
option = defaultVersioningRule.getOptions().get(lifecycleState);
}
if (option != null) {
return option.getVersioningOptionList();
}
if (PROJECT_STATE.equals(lifecycleState)
|| APPROVED_STATE.equals(lifecycleState)
|| OBSOLETE_STATE.equals(lifecycleState)) {
return Arrays.asList(NONE, MINOR, MAJOR);
}
if (FILE_TYPE.equals(type) || NOTE_TYPE.equals(type)) {
return Arrays.asList(NONE, MINOR, MAJOR);
}
return Arrays.asList(NONE);
}
protected VersioningOption validateOption(Document doc,
VersioningOption option) throws DocumentException {
List<VersioningOption> options = getSaveOptions(doc);
if (!options.contains(option)) {
option = options.get(0);
}
return option;
}
@Override
public VersioningOption doPreSave(Document doc, boolean isDirty,
VersioningOption option, String checkinComment,
Map<String, Serializable> options) throws DocumentException {
option = validateOption(doc, option);
if (!doc.isCheckedOut() && isDirty) {
doc.checkOut();
followTransitionByOption(doc, option);
}
// transition follow shouldn't change what postSave options will be
return option;
}
protected void followTransitionByOption(Document doc,
VersioningOption option) throws DocumentException {
try {
String lifecycleState = doc.getLifeCycleState();
if (APPROVED_STATE.equals(lifecycleState)
|| OBSOLETE_STATE.equals(lifecycleState)) {
doc.followTransition(BACK_TO_PROJECT_TRANSITION);
}
} catch (LifeCycleException e) {
throw new DocumentException(e);
}
}
@Override
public Document doPostSave(Document doc, VersioningOption option,
String checkinComment, Map<String, Serializable> options)
throws DocumentException {
// option = validateOption(doc, option); // validated before
boolean increment = option != NONE;
if (doc.isCheckedOut() && increment) {
incrementByOption(doc, option);
return doc.checkIn(null, checkinComment); // auto-label
}
return null;
}
@Override
public Document doCheckIn(Document doc, VersioningOption option,
String checkinComment) throws DocumentException {
incrementByOption(doc, option == MAJOR ? MAJOR : MINOR);
return doc.checkIn(null, checkinComment); // auto-label
}
@Override
public void doCheckOut(Document doc) throws DocumentException {
doc.checkOut();
// set version number to that of the last version
try {
Document last = doc.getLastVersion();
if (last != null) {
setVersion(doc, getMajor(last), getMinor(last));
}
} catch (NoSuchPropertyException e) {
// ignore
}
}
@Override
public Map<String, VersioningRuleDescriptor> getVersioningRules() {
return versioningRules;
}
@Override
public void setVersioningRules(
Map<String, VersioningRuleDescriptor> versioningRules) {
this.versioningRules = versioningRules;
}
@Override
public void setDefaultVersioningRule(
DefaultVersioningRuleDescriptor defaultVersioningRule) {
this.defaultVersioningRule = defaultVersioningRule;
}
}