1 package org.argeo.connect.versioning;
2
3 import static java.util.Arrays.asList;
4
5 import java.util.ArrayList;
6 import java.util.Calendar;
7 import java.util.Iterator;
8 import java.util.LinkedHashMap;
9 import java.util.List;
10 import java.util.Map;
11
12 import javax.jcr.Item;
13 import javax.jcr.Node;
14 import javax.jcr.NodeIterator;
15 import javax.jcr.Property;
16 import javax.jcr.PropertyIterator;
17 import javax.jcr.RepositoryException;
18 import javax.jcr.Session;
19 import javax.jcr.Value;
20 import javax.jcr.version.Version;
21 import javax.jcr.version.VersionHistory;
22 import javax.jcr.version.VersionIterator;
23 import javax.jcr.version.VersionManager;
24
25 import org.argeo.connect.ConnectException;
26
27
28 public class VersionUtils {
29
30
31 public static final List<String> DEFAULT_FILTERED_OUT_PROP_NAMES = asList("jcr:uuid", "jcr:frozenUuid",
32 "jcr:frozenPrimaryType", "jcr:primaryType", "jcr:lastModified", "jcr:lastModifiedBy",
33 Property.JCR_LAST_MODIFIED_BY);
34
35 public static List<VersionDiff> listHistoryDiff(Node entity, List<String> excludedProperties) {
36 try {
37 Session session = entity.getSession();
38 List<VersionDiff> res = new ArrayList<VersionDiff>();
39 VersionManager versionManager = session.getWorkspace().getVersionManager();
40
41
42
43
44
45 VersionHistory versionHistory = null;
46 try {
47 versionHistory = versionManager.getVersionHistory(entity.getPath());
48 } catch (Exception ise) {
49
50
51
52
53
54
55 return res;
56 }
57
58 VersionIterator vit = versionHistory.getAllLinearVersions();
59
60 while (vit.hasNext()) {
61 Version version = vit.nextVersion();
62 Node node = version.getFrozenNode();
63
64 Version predecessor = null;
65 try {
66 predecessor = version.getLinearPredecessor();
67 } catch (Exception e) {
68
69
70 }
71 if (predecessor == null) {
72 } else {
73 Map<String, ItemDiff> diffs = VersionUtils.compareNodes(predecessor.getFrozenNode(), node,
74 excludedProperties);
75 if (!diffs.isEmpty()) {
76 String userid = node.hasProperty(Property.JCR_LAST_MODIFIED_BY)
77 ? node.getProperty(Property.JCR_LAST_MODIFIED_BY).getString() : null;
78 Calendar updateTime = node.hasProperty(Property.JCR_LAST_MODIFIED)
79 ? node.getProperty(Property.JCR_LAST_MODIFIED).getDate() : null;
80 res.add(new VersionDiff(null, userid, updateTime, diffs));
81 }
82 }
83 }
84 return res;
85 } catch (RepositoryException e) {
86 throw new ConnectException("Cannot generate history for node " + entity, e);
87 }
88 }
89
90
91
92
93
94 public static Map<String, ItemDiff> compareNodes(Node reference, Node observed, List<String> excludedProperties) {
95
96 Map<String, ItemDiff> diffs = new LinkedHashMap<String, ItemDiff>();
97 compareNodes(diffs, null, reference, observed, excludedProperties);
98 return diffs;
99 }
100
101
102 static void compareNodes(Map<String, ItemDiff> diffs, String relPath, Node reference, Node observed,
103 List<String> excludedProperties) {
104 Map<String, ItemDiff> localDiffs = new LinkedHashMap<String, ItemDiff>();
105 try {
106 compareProperties(localDiffs, relPath, reference, observed, excludedProperties);
107
108
109 NodeIterator nit = reference.getNodes();
110 while (nit.hasNext()) {
111 Node n = nit.nextNode();
112 String refRelPath = getRelPath(reference, n);
113
114 String currNodePath = (relPath != null ? relPath + "/" : "") + refRelPath;
115 if (observed.hasNode(refRelPath)) {
116 Map<String, ItemDiff> modDiffs = new LinkedHashMap<String, ItemDiff>();
117 compareNodes(modDiffs, currNodePath, n, observed.getNode(refRelPath), excludedProperties);
118 if (!modDiffs.isEmpty()) {
119 ItemDiff iDiff = new ItemDiff(ItemDiff.MODIFIED, currNodePath, n, observed.getNode(refRelPath));
120 localDiffs.put(currNodePath, iDiff);
121 localDiffs.putAll(modDiffs);
122 }
123 } else {
124 ItemDiff iDiff = new ItemDiff(ItemDiff.REMOVED, currNodePath, n, null);
125 localDiffs.put(currNodePath, iDiff);
126 addAllProperties(localDiffs, ItemDiff.REMOVED, true, n, excludedProperties);
127 }
128 }
129
130 nit = observed.getNodes();
131 while (nit.hasNext()) {
132 Node n = nit.nextNode();
133 String obsRelPath = getRelPath(observed, n);
134 String currNodePath = (relPath != null ? relPath + "/" : "") + obsRelPath;
135 if (!reference.hasNode(obsRelPath)) {
136 ItemDiff iDiff = new ItemDiff(ItemDiff.ADDED, currNodePath, null, n);
137 localDiffs.put(currNodePath, iDiff);
138
139
140
141
142
143
144 }
145 }
146
147 if (!localDiffs.isEmpty()) {
148
149
150
151
152 if (localDiffs.size() >= 2) {
153 Iterator<ItemDiff> it = localDiffs.values().iterator();
154 if (isNodeDiff(it.next()) && isNodeDiff(it.next())) {
155
156 localDiffs.remove(localDiffs.keySet().iterator().next());
157 }
158 }
159 diffs.putAll(localDiffs);
160 }
161 } catch (RepositoryException e) {
162 throw new ConnectException("Cannot diff " + reference + " and " + observed, e);
163 }
164 }
165
166 static public boolean isNodeDiff(ItemDiff diff) {
167 Item refItem = diff.getReferenceItem();
168 Item newItem = diff.getObservedItem();
169 Item tmpItem = refItem == null ? newItem : refItem;
170 return tmpItem instanceof Node;
171 }
172
173 static void addAllProperties(Map<String, ItemDiff> diffs, Integer type, boolean trackSubNode, Node node,
174 List<String> excludedProperties) throws RepositoryException {
175 PropertyIterator pit = node.getProperties();
176 props: while (pit.hasNext()) {
177 Property p = pit.nextProperty();
178 String name = p.getName();
179 if (excludedProperties.contains(name))
180 continue props;
181 ItemDiff iDiff;
182 if (ItemDiff.ADDED == type)
183 iDiff = new ItemDiff(ItemDiff.ADDED, name, null, p);
184 else
185 iDiff = new ItemDiff(ItemDiff.REMOVED, name, p, null);
186 diffs.put(name, iDiff);
187 }
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217 }
218
219
220
221
222
223
224
225
226
227 static void compareProperties(Map<String, ItemDiff> diffs, String relPath, Node reference, Node observed,
228 List<String> excludedProperties) {
229 try {
230
231 PropertyIterator pit = reference.getProperties();
232 props: while (pit.hasNext()) {
233 Property p = pit.nextProperty();
234 String name = p.getName();
235 String relName = propertyRelPath(relPath, name);
236 if (excludedProperties.contains(name))
237 continue props;
238 if (!observed.hasProperty(name)) {
239 ItemDiff iDiff = new ItemDiff(ItemDiff.REMOVED, name, p, null);
240 diffs.put(relName, iDiff);
241 } else {
242 if (p.isMultiple()) {
243
244 Value[] refValues = p.getValues();
245 Value[] newValues = observed.getProperty(name).getValues();
246 refValues: for (Value refValue : refValues) {
247 for (Value newValue : newValues) {
248 if (refValue.equals(newValue))
249 continue refValues;
250 }
251
252
253 ItemDiff iDiff = new ItemDiff(ItemDiff.MODIFIED, name, p, observed.getProperty(name));
254 diffs.put(relName, iDiff);
255 continue props;
256 }
257
258 newValues: for (Value newValue : newValues) {
259 for (Value refValue : refValues) {
260 if (refValue.equals(newValue))
261 continue newValues;
262 }
263
264
265 ItemDiff iDiff = new ItemDiff(ItemDiff.MODIFIED, name, p, observed.getProperty(name));
266 diffs.put(relName, iDiff);
267 continue props;
268 }
269 } else {
270 Value referenceValue = p.getValue();
271 Value newValue = observed.getProperty(name).getValue();
272 if (!referenceValue.equals(newValue)) {
273 ItemDiff iDiff = new ItemDiff(ItemDiff.MODIFIED, name, p, observed.getProperty(name));
274 diffs.put(relName, iDiff);
275 }
276 }
277 }
278 }
279
280 pit = observed.getProperties();
281 props: while (pit.hasNext()) {
282 Property p = pit.nextProperty();
283 String name = p.getName();
284 String relName = propertyRelPath(relPath, name);
285 if (excludedProperties.contains(name))
286 continue props;
287 if (!reference.hasProperty(name)) {
288 ItemDiff pDiff = new ItemDiff(ItemDiff.ADDED, name, null, p);
289 diffs.put(relName, pDiff);
290 }
291 }
292 } catch (RepositoryException e) {
293 throw new ConnectException("Cannot diff " + reference + " and " + observed, e);
294 }
295 }
296
297 private static String getRelPath(Node parent, Node descendant) throws RepositoryException {
298 String pPath = parent.getPath();
299 String dPath = descendant.getPath();
300 if (!dPath.startsWith(pPath))
301 throw new ConnectException(
302 "Cannot get rel path for " + descendant + ". It is not a descendant of " + parent);
303 String relPath = dPath.substring(pPath.length());
304 if (relPath.startsWith("/"))
305 relPath = relPath.substring(1);
306 return relPath;
307 }
308
309
310 private static String propertyRelPath(String baseRelPath, String propertyName) {
311 if (baseRelPath == null)
312 return propertyName;
313 else
314 return baseRelPath + '/' + propertyName;
315 }
316 }