VersionHistoryUtils.java
/*
* Copyright (C) 20016 eXo Platform.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.services.cms.documents;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.version.Version;
import javax.jcr.version.VersionHistory;
import javax.jcr.version.VersionIterator;
import java.util.*;
public class VersionHistoryUtils {
protected static final Log log = ExoLogger.getLogger(VersionHistoryUtils.class);
private static final int DOCUMENT_AUTO_DEFAULT_VERSION_MAX = 0;
private static final int DOCUMENT_AUTO_DEFAULT_VERSION_EXPIRED = 0;
public static final String NT_FILE = "nt:file";
public static final String MIX_VERSIONABLE = "mix:versionable";
//Mixin used to store display version Name added after remove base version
public static final String MIX_DISPLAY_VERSION_NAME = "mix:versionDisplayName";
//mav version , incremented by 1 after add a new version
public static final String MAX_VERSION_PROPERTY = "exo:maxVersion";
//list version name (jcr Name, Display Name)
public static final String LIST_VERSION_PROPERTY = "exo:versionList";
public static final String VERSION_SEPARATOR = ":";
private static String maxAllowVersionProp = "jcr.documents.versions.max";
private static String expirationTimeProp = "jcr.documents.versions.expiration";
private static int maxAllowVersion;
private static long maxLiveTime;
static {
try {
maxAllowVersion = Integer.parseInt(System.getProperty(maxAllowVersionProp));
maxLiveTime = Integer.parseInt(System.getProperty(expirationTimeProp));
//ignore invalid input config
if(maxAllowVersion < 0) maxAllowVersion = DOCUMENT_AUTO_DEFAULT_VERSION_MAX;
if(maxLiveTime < 0) maxLiveTime = DOCUMENT_AUTO_DEFAULT_VERSION_EXPIRED;
}catch(NumberFormatException nex){
maxAllowVersion = DOCUMENT_AUTO_DEFAULT_VERSION_MAX;
maxLiveTime = DOCUMENT_AUTO_DEFAULT_VERSION_EXPIRED;
}
maxLiveTime = maxLiveTime * 24 * 60 * 60 * 1000;
}
/**
* Create new version and clear redundant versions
*
* @param nodeVersioning
*/
public static Version createVersion(Node nodeVersioning) throws Exception {
if(!nodeVersioning.isNodeType(NT_FILE)) {
if(log.isDebugEnabled()){
log.debug("Version history is not impact with non-nt:file documents, there'is not any version created.");
}
return null;
}
if(!nodeVersioning.isNodeType(MIX_VERSIONABLE)){
if(nodeVersioning.canAddMixin(MIX_VERSIONABLE)) {
nodeVersioning.addMixin(MIX_VERSIONABLE);
nodeVersioning.save();
}
return null;
}
Version version = null;
if (!nodeVersioning.isCheckedOut()) {
nodeVersioning.checkout();
version = nodeVersioning.getBaseVersion();
} else {
version = nodeVersioning.checkin();
nodeVersioning.checkout();
}
//check if mixin mix:versionDisplayName is added then,
//update max version after add new version (increment by 1)
if (version != null && nodeVersioning.isNodeType(VersionHistoryUtils.MIX_DISPLAY_VERSION_NAME)) {
int maxVersion = 0;
if(nodeVersioning.hasProperty(MAX_VERSION_PROPERTY)){
//Get old max version ID
maxVersion = (int) nodeVersioning.getProperty(VersionHistoryUtils.MAX_VERSION_PROPERTY).getLong();
//Update max version IX (maxVersion+1)
nodeVersioning.setProperty(MAX_VERSION_PROPERTY, maxVersion +1);
}
//add a new entry to store the display version for the new added version (jcrID, maxVersion)
String newRef = version.getName() + VERSION_SEPARATOR + maxVersion;
List<Value> newValues = new ArrayList<Value>();
Value[] values;
if(nodeVersioning.hasProperty(LIST_VERSION_PROPERTY)){
values = nodeVersioning.getProperty(LIST_VERSION_PROPERTY).getValues();
newValues.addAll(Arrays.<Value>asList(values));
}
Value value2add = nodeVersioning.getSession().getValueFactory().createValue(newRef);
newValues.add(value2add);
//Update the list version entries
nodeVersioning.setProperty(VersionHistoryUtils.LIST_VERSION_PROPERTY, newValues.toArray(new Value[newValues.size()]));
nodeVersioning.save();
}
if(maxAllowVersion!= DOCUMENT_AUTO_DEFAULT_VERSION_MAX || maxLiveTime != DOCUMENT_AUTO_DEFAULT_VERSION_EXPIRED) {
removeRedundant(nodeVersioning);
}
nodeVersioning.save();
return version;
}
/**
* remove version
*
* @param node root Node
* @param versionName version name to remove
*/
public static void removeVersion(Node node, String versionName) throws RepositoryException {
VersionHistory versionHistory = node.getVersionHistory();
//Case of remove Base Version, Add mixin mix:versionDisplayName
if (node.getBaseVersion().getName().equals(versionName)) {
if (node.canAddMixin(MIX_DISPLAY_VERSION_NAME)) {
//Init the maxVersion = versionID(base Version) + 1
int maxVersion = Integer.parseInt(versionName) + 1;
node.addMixin(MIX_DISPLAY_VERSION_NAME);
node.setProperty(MAX_VERSION_PROPERTY, maxVersion);
} else {
//remove entry of removed version
removeRefVersionName(node, versionName);
}
node.save();
} else if (node.isNodeType(MIX_DISPLAY_VERSION_NAME)) {
//remove entry of removed version
removeRefVersionName(node, versionName);
node.save();
}
versionHistory.removeVersion(versionName);
}
/**
* Remove redundant version
* - Remove versions has been expired
* - Remove versions over max allow
* @param nodeVersioning
* @throws Exception
*/
private static void removeRedundant(Node nodeVersioning) throws Exception{
VersionHistory versionHistory = nodeVersioning.getVersionHistory();
String baseVersion = nodeVersioning.getBaseVersion().getName();
String rootVersion = nodeVersioning.getVersionHistory().getRootVersion().getName();
VersionIterator versions = versionHistory.getAllVersions();
Date currentDate = new Date();
Map<String, String> lstVersions = new HashMap<String, String>();
List<String> lstVersionTime = new ArrayList<String>();
while (versions.hasNext()) {
Version version = versions.nextVersion();
if(rootVersion.equals(version.getName()) || baseVersion.equals(version.getName())) continue;
if (maxLiveTime!= DOCUMENT_AUTO_DEFAULT_VERSION_EXPIRED &&
currentDate.getTime() - version.getCreated().getTime().getTime() > maxLiveTime) {
versionHistory.removeVersion(version.getName());
} else {
lstVersions.put(String.valueOf(version.getCreated().getTimeInMillis()), version.getName());
lstVersionTime.add(String.valueOf(version.getCreated().getTimeInMillis()));
}
}
if (maxAllowVersion <= lstVersionTime.size() && maxAllowVersion!= DOCUMENT_AUTO_DEFAULT_VERSION_MAX) {
Collections.sort(lstVersionTime);
String[] lsts = lstVersionTime.toArray(new String[lstVersionTime.size()]);
for (int j = 0; j <= lsts.length - maxAllowVersion; j++) {
versionHistory.removeVersion(lstVersions.get(lsts[j]));
}
}
}
/**
* Remove version Name entry from the list of versions
* @param node
* @param versionName
* @throws RepositoryException
*/
private static void removeRefVersionName(Node node, String versionName) throws RepositoryException {
if(node.hasProperty(LIST_VERSION_PROPERTY)){
if(node.hasProperty(VersionHistoryUtils.LIST_VERSION_PROPERTY)){
Value[] values = node.getProperty(VersionHistoryUtils.LIST_VERSION_PROPERTY).getValues();
List<Value> newValues = new ArrayList<Value>();
for (Value value : values){
if (!value.getString().split(VERSION_SEPARATOR)[0].equals(versionName))
newValues.add(value);
}
node.setProperty(VersionHistoryUtils.LIST_VERSION_PROPERTY, newValues.toArray(new Value[newValues.size()]));
}
}
}
}