View Javadoc
1   /*
2    * Copyright (C) 20016 eXo Platform.
3    *
4    * This is free software; you can redistribute it and/or modify it
5    * under the terms of the GNU Lesser General Public License as
6    * published by the Free Software Foundation; either version 2.1 of
7    * the License, or (at your option) any later version.
8    *
9    * This software is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12   * Lesser General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this software; if not, write to the Free
16   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18   */
19  package org.exoplatform.services.cms.documents;
20  
21  import org.exoplatform.services.log.ExoLogger;
22  import org.exoplatform.services.log.Log;
23  
24  import javax.jcr.Node;
25  import javax.jcr.RepositoryException;
26  import javax.jcr.Value;
27  import javax.jcr.version.Version;
28  import javax.jcr.version.VersionHistory;
29  import javax.jcr.version.VersionIterator;
30  import java.util.*;
31  
32  public class VersionHistoryUtils {
33  
34    protected static final Log log = ExoLogger.getLogger(VersionHistoryUtils.class);
35  
36    private static final int DOCUMENT_AUTO_DEFAULT_VERSION_MAX = 0;
37    private static final int DOCUMENT_AUTO_DEFAULT_VERSION_EXPIRED = 0;
38  
39    public static final String NT_FILE          = "nt:file";
40    public static final String MIX_VERSIONABLE  = "mix:versionable";
41  
42    //Mixin used to store display version Name added after remove base version
43    public static  final String MIX_DISPLAY_VERSION_NAME = "mix:versionDisplayName";
44    //mav version , incremented by 1 after add a new version
45    public static  final String MAX_VERSION_PROPERTY = "exo:maxVersion";
46    //list version name (jcr Name, Display Name)
47    public static  final String LIST_VERSION_PROPERTY = "exo:versionList";
48    public static  final String VERSION_SEPARATOR = ":";
49  
50    private static String maxAllowVersionProp   = "jcr.documents.versions.max";
51    private static String expirationTimeProp    = "jcr.documents.versions.expiration";
52  
53    private static int maxAllowVersion;
54    private static long maxLiveTime;
55  
56    static {
57      try {
58        maxAllowVersion = Integer.parseInt(System.getProperty(maxAllowVersionProp));
59        maxLiveTime = Integer.parseInt(System.getProperty(expirationTimeProp));
60        //ignore invalid input config
61        if(maxAllowVersion < 0) maxAllowVersion = DOCUMENT_AUTO_DEFAULT_VERSION_MAX;
62        if(maxLiveTime < 0) maxLiveTime = DOCUMENT_AUTO_DEFAULT_VERSION_EXPIRED;
63      }catch(NumberFormatException nex){
64        maxAllowVersion = DOCUMENT_AUTO_DEFAULT_VERSION_MAX;
65        maxLiveTime = DOCUMENT_AUTO_DEFAULT_VERSION_EXPIRED;
66      }
67      maxLiveTime = maxLiveTime * 24 * 60 * 60 * 1000;
68    }
69  
70    /**
71     * Create new version and clear redundant versions
72     *
73     * @param nodeVersioning
74     */
75    public static Version createVersion(Node nodeVersioning) throws Exception {
76      if(!nodeVersioning.isNodeType(NT_FILE)) {
77        if(log.isDebugEnabled()){
78          log.debug("Version history is not impact with non-nt:file documents, there'is not any version created.");
79        }
80        return null;
81      }
82  
83      if(!nodeVersioning.isNodeType(MIX_VERSIONABLE)){
84        if(nodeVersioning.canAddMixin(MIX_VERSIONABLE)) {
85          nodeVersioning.addMixin(MIX_VERSIONABLE);
86          nodeVersioning.save();
87        }
88        return null;
89      }
90      Version version = null;
91      if (!nodeVersioning.isCheckedOut()) {
92        nodeVersioning.checkout();
93        version = nodeVersioning.getBaseVersion();
94      } else {
95        version = nodeVersioning.checkin();
96        nodeVersioning.checkout();
97      }
98  
99      //check if mixin mix:versionDisplayName is added then,
100     //update max version after add new version (increment by 1)
101     if (version != null && nodeVersioning.isNodeType(VersionHistoryUtils.MIX_DISPLAY_VERSION_NAME)) {
102       int maxVersion = 0;
103       if(nodeVersioning.hasProperty(MAX_VERSION_PROPERTY)){
104         //Get old max version ID
105         maxVersion = (int) nodeVersioning.getProperty(VersionHistoryUtils.MAX_VERSION_PROPERTY).getLong();
106         //Update max version IX (maxVersion+1)
107         nodeVersioning.setProperty(MAX_VERSION_PROPERTY, maxVersion +1);
108       }
109       //add a new entry to store the display version for the new added version (jcrID, maxVersion)
110       String newRef = version.getName() + VERSION_SEPARATOR + maxVersion;
111       List<Value> newValues = new ArrayList<Value>();
112       Value[] values;
113       if(nodeVersioning.hasProperty(LIST_VERSION_PROPERTY)){
114         values = nodeVersioning.getProperty(LIST_VERSION_PROPERTY).getValues();
115         newValues.addAll(Arrays.<Value>asList(values));
116       }
117       Value value2add = nodeVersioning.getSession().getValueFactory().createValue(newRef);
118       newValues.add(value2add);
119       //Update the list version entries
120       nodeVersioning.setProperty(VersionHistoryUtils.LIST_VERSION_PROPERTY, newValues.toArray(new Value[newValues.size()]));
121       nodeVersioning.save();
122     }
123 
124     if(maxAllowVersion!= DOCUMENT_AUTO_DEFAULT_VERSION_MAX || maxLiveTime != DOCUMENT_AUTO_DEFAULT_VERSION_EXPIRED) {
125       removeRedundant(nodeVersioning);
126     }
127     nodeVersioning.save();
128     return version;
129   }
130 
131   /**
132    * remove  version
133    *
134    * @param node root Node
135    * @param  versionName version name to remove
136    */
137   public static void removeVersion(Node node, String versionName) throws RepositoryException {
138     VersionHistory versionHistory = node.getVersionHistory();
139 
140     //Case of remove Base Version, Add mixin mix:versionDisplayName
141     if (node.getBaseVersion().getName().equals(versionName)) {
142       if (node.canAddMixin(MIX_DISPLAY_VERSION_NAME)) {
143         //Init the maxVersion = versionID(base Version) + 1
144         int maxVersion  = Integer.parseInt(versionName) + 1;
145         node.addMixin(MIX_DISPLAY_VERSION_NAME);
146         node.setProperty(MAX_VERSION_PROPERTY, maxVersion);
147       } else {
148         //remove entry of removed version
149         removeRefVersionName(node, versionName);
150       }
151       node.save();
152     } else if (node.isNodeType(MIX_DISPLAY_VERSION_NAME)) {
153       //remove entry of removed version
154       removeRefVersionName(node, versionName);
155       node.save();
156     }
157     versionHistory.removeVersion(versionName);
158   }
159 
160   /**
161    * Remove redundant version
162    * - Remove versions has been expired
163    * - Remove versions over max allow
164    * @param nodeVersioning
165    * @throws Exception
166    */
167   private static void removeRedundant(Node nodeVersioning) throws Exception{
168     VersionHistory versionHistory = nodeVersioning.getVersionHistory();
169     String baseVersion = nodeVersioning.getBaseVersion().getName();
170     String rootVersion = nodeVersioning.getVersionHistory().getRootVersion().getName();
171     VersionIterator versions = versionHistory.getAllVersions();
172     Date currentDate = new Date();
173     Map<String, String> lstVersions = new HashMap<String, String>();
174     List<String> lstVersionTime = new ArrayList<String>();
175     while (versions.hasNext()) {
176       Version version = versions.nextVersion();
177       if(rootVersion.equals(version.getName()) || baseVersion.equals(version.getName())) continue;
178 
179       if (maxLiveTime!= DOCUMENT_AUTO_DEFAULT_VERSION_EXPIRED &&
180               currentDate.getTime() - version.getCreated().getTime().getTime() > maxLiveTime) {
181         versionHistory.removeVersion(version.getName());
182       } else {
183         lstVersions.put(String.valueOf(version.getCreated().getTimeInMillis()), version.getName());
184         lstVersionTime.add(String.valueOf(version.getCreated().getTimeInMillis()));
185       }
186     }
187     if (maxAllowVersion <= lstVersionTime.size() && maxAllowVersion!= DOCUMENT_AUTO_DEFAULT_VERSION_MAX) {
188       Collections.sort(lstVersionTime);
189       String[] lsts = lstVersionTime.toArray(new String[lstVersionTime.size()]);
190       for (int j = 0; j <= lsts.length - maxAllowVersion; j++) {
191         versionHistory.removeVersion(lstVersions.get(lsts[j]));
192       }
193     }
194   }
195 
196   /**
197    * Remove version Name entry from the list of versions
198    * @param node
199    * @param versionName
200    * @throws RepositoryException
201    */
202   private static void removeRefVersionName(Node node, String versionName) throws RepositoryException {
203     if(node.hasProperty(LIST_VERSION_PROPERTY)){
204       if(node.hasProperty(VersionHistoryUtils.LIST_VERSION_PROPERTY)){
205         Value[] values = node.getProperty(VersionHistoryUtils.LIST_VERSION_PROPERTY).getValues();
206         List<Value> newValues = new ArrayList<Value>();
207         for (Value value : values){
208           if (!value.getString().split(VERSION_SEPARATOR)[0].equals(versionName))
209             newValues.add(value);
210         }
211         node.setProperty(VersionHistoryUtils.LIST_VERSION_PROPERTY, newValues.toArray(new Value[newValues.size()]));
212       }
213     }
214   }
215 }