1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.jcr.wrapper;
35
36 import info.magnolia.cms.security.User;
37 import info.magnolia.context.MgnlContext;
38 import info.magnolia.jcr.decoration.ContentDecorator;
39 import info.magnolia.jcr.decoration.ContentDecoratorPropertyWrapper;
40 import info.magnolia.jcr.decoration.ContentDecoratorSessionWrapper;
41 import info.magnolia.jcr.decoration.ContentDecoratorWorkspaceWrapper;
42 import info.magnolia.jcr.util.NodeTypes;
43 import info.magnolia.jcr.util.NodeTypes.LastModified;
44 import info.magnolia.repository.RepositoryConstants;
45
46 import java.io.InputStream;
47 import java.math.BigDecimal;
48 import java.util.ArrayList;
49 import java.util.Calendar;
50 import java.util.List;
51 import java.util.Map;
52
53 import javax.jcr.AccessDeniedException;
54 import javax.jcr.Binary;
55 import javax.jcr.InvalidItemStateException;
56 import javax.jcr.ItemExistsException;
57 import javax.jcr.NoSuchWorkspaceException;
58 import javax.jcr.Node;
59 import javax.jcr.NodeIterator;
60 import javax.jcr.PathNotFoundException;
61 import javax.jcr.Property;
62 import javax.jcr.ReferentialIntegrityException;
63 import javax.jcr.RepositoryException;
64 import javax.jcr.Session;
65 import javax.jcr.Value;
66 import javax.jcr.ValueFormatException;
67 import javax.jcr.Workspace;
68 import javax.jcr.lock.LockException;
69 import javax.jcr.nodetype.ConstraintViolationException;
70 import javax.jcr.nodetype.NoSuchNodeTypeException;
71 import javax.jcr.version.VersionException;
72
73 import org.apache.commons.lang.ArrayUtils;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76
77
78
79
80 public class LastUpdateContentDecorator extends PropertyAndChildWrappingContentDecorator implements ContentDecorator {
81
82 private static final Logger log = LoggerFactory.getLogger(LastUpdateContentDecorator.class);
83
84
85
86
87
88 private static Map<String, String> parentNodeMappings = ArrayUtils.toMap(new String[][] {
89 { RepositoryConstants.WEBSITE, NodeTypes.Page.NAME },
90 { RepositoryConstants.USERS, NodeTypes.User.NAME } });
91
92 protected boolean isSysSessionDirty;
93
94
95
96
97
98 final class ChangeLastUpdateDateOp extends MgnlContext.RepositoryOp {
99 private final String workspaceName;
100 private final String username;
101 private final String destAbsPath;
102 private final Calendar updateDate;
103 private final boolean recursiveDown;
104
105 ChangeLastUpdateDateOp(String workspaceName, String username, String destAbsPath, Calendar updateDate, boolean recursiveDown) {
106 this.workspaceName = workspaceName;
107 this.username = username;
108 this.destAbsPath = destAbsPath;
109 this.updateDate = updateDate;
110 this.recursiveDown = recursiveDown;
111 }
112
113 @Override
114 public void doExec() throws RepositoryException {
115 Session sysSession = MgnlContext.getJCRSession(workspaceName);
116
117
118 if (!sysSession.itemExists(destAbsPath)) {
119
120 if (log.isDebugEnabled()) {
121 log.warn("Can't update mgnl:lastModified. Path {}:{} doesn't exist anymore.", workspaceName, destAbsPath);
122 }
123 return;
124 }
125 Node node = null;
126
127 node = resolveModifiedNode(workspaceName, destAbsPath, sysSession);
128
129
130 if (node == null) {
131 node = sysSession.getNode(destAbsPath);
132 }
133
134 if (node.isNodeType(NodeTypes.MetaData.NAME)) {
135
136 return;
137 }
138
139
140 if (node.getDepth() == 0) {
141 return;
142 }
143
144
145 if (node instanceof DelegateNodeWrapper) {
146 node = ((DelegateNodeWrapper) node).deepUnwrap(LastUpdateNodeWrapper.class);
147 }
148
149 log.debug("LUD on {} from {}:{}", node.getPath(), node.getSession().toString(), Thread.currentThread().getName());
150 if (node.isNodeType(NodeTypes.LastModified.NAME)) {
151 NodeTypes.LastModified.update(node);
152 }
153
154 if (recursiveDown) {
155
156 List<NodeIterator> iters = new ArrayList<NodeIterator>();
157 iters.add(node.getNodes());
158 while (!iters.isEmpty()) {
159 List<NodeIterator> tmp = updateChildren(iters, username, updateDate);
160 iters.clear();
161 iters.addAll(tmp);
162 }
163 }
164
165
166 isSysSessionDirty = true;
167 return;
168 }
169
170 private Node resolveModifiedNode(final String workspaceName, final String destAbsPath, Session sysSession) throws RepositoryException {
171 String parentNodeType = parentNodeMappings.get(workspaceName);
172 Node node = sysSession.getNode(destAbsPath);
173 if (parentNodeType == null) {
174 return node;
175 }
176 while (node != null && !parentNodeType.equals(node.getPrimaryNodeType().getName()) && node.getDepth() > 0) {
177 node = node.getParent();
178 }
179 return node;
180 }
181
182 private List<NodeIterator> updateChildren(List<NodeIterator> iters, String username, Calendar updateDate) {
183 List<NodeIterator> tmp = new ArrayList<NodeIterator>();
184 for (NodeIterator iter : iters) {
185 while (iter.hasNext()) {
186 Node node = iter.nextNode();
187 try {
188 if (RepositoryConstants.WEBSITE.equals(workspaceName) && !NodeTypes.Page.NAME.equals(node.getPrimaryNodeType().getName())) {
189
190 continue;
191 }
192 if (node.isNodeType(NodeTypes.MetaData.NAME)) {
193
194 continue;
195 }
196 if (node.isNodeType(NodeTypes.LastModified.NAME)) {
197 LastModified.update(node, username, updateDate);
198 }
199 tmp.add(node.getNodes());
200 } catch (RepositoryException e) {
201 log.error("Failed to update last modified date of " + node + " with " + e.getMessage(), e);
202 }
203 }
204 }
205 return tmp;
206 }
207 }
208
209
210
211
212 public class LastUpdatePropertyWrapper extends ContentDecoratorPropertyWrapper<LastUpdateContentDecorator> implements Property {
213
214 public LastUpdatePropertyWrapper(Property property, LastUpdateContentDecorator contentDecorator) {
215 super(property, contentDecorator);
216 }
217
218 @Override
219 public void setValue(BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
220 super.setValue(value);
221 this.updateLastModifiedProperty();
222 }
223
224 @Override
225 public void setValue(Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
226 super.setValue(value);
227 this.updateLastModifiedProperty();
228 }
229
230 @Override
231 public void setValue(boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
232 super.setValue(value);
233 this.updateLastModifiedProperty();
234 }
235
236 @Override
237 public void setValue(Calendar value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
238 super.setValue(value);
239 this.updateLastModifiedProperty();
240 }
241
242 @Override
243 public void setValue(double value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
244 super.setValue(value);
245 this.updateLastModifiedProperty();
246 }
247
248 @Override
249 public void setValue(InputStream value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
250 super.setValue(value);
251 this.updateLastModifiedProperty();
252 }
253
254 @Override
255 public void setValue(long value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
256 super.setValue(value);
257 this.updateLastModifiedProperty();
258 }
259
260 @Override
261 public void setValue(Node value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
262 super.setValue(value);
263 this.updateLastModifiedProperty();
264 }
265
266 @Override
267 public void setValue(String value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
268 super.setValue(value);
269 this.updateLastModifiedProperty();
270 }
271
272 @Override
273 public void setValue(String[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
274 super.setValue(values);
275 this.updateLastModifiedProperty();
276 }
277
278 @Override
279 public void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
280 super.setValue(value);
281 this.updateLastModifiedProperty();
282 }
283
284 @Override
285 public void setValue(Value[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
286 super.setValue(values);
287 this.updateLastModifiedProperty();
288 }
289
290 @Override
291 public void remove() throws VersionException, LockException, ConstraintViolationException, AccessDeniedException, RepositoryException {
292 Node parent = this.getParent();
293 String propertyName = this.getName();
294 super.remove();
295 if (shouldIgnoreUpdate(propertyName)) {
296 return;
297 }
298 updateLastModified(parent.getSession(), parent.getPath());
299 }
300
301 private void updateLastModifiedProperty() throws RepositoryException {
302 LastUpdateContentDecorator.this.updateLastModifiedProperty(getSession().getWorkspace().getName(), this.getName(), this.getParent().getPath());
303 }
304 }
305
306
307
308
309 public class LastUpdateWorkspaceWrapper extends ContentDecoratorWorkspaceWrapper implements Workspace {
310
311 protected LastUpdateWorkspaceWrapper(Workspace workspace, ContentDecorator contentDecorator) {
312 super(workspace, contentDecorator);
313 }
314
315 @Override
316 public void move(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
317 super.move(srcAbsPath, destAbsPath);
318 updateLastModified(super.getWrappedWorkspace().getSession(), destAbsPath, true);
319 }
320
321 @Override
322 public void copy(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
323 super.copy(srcAbsPath, destAbsPath);
324 updateLastModified(super.getWrappedWorkspace().getSession(), destAbsPath, true);
325 }
326
327 @Override
328 public void copy(String srcWorkspace, String srcAbsPath, String destAbsPath) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
329 super.copy(srcWorkspace, srcAbsPath, destAbsPath);
330 updateLastModified(super.getWrappedWorkspace().getSession(), destAbsPath, true);
331 }
332
333 @Override
334 public void clone(String srcWorkspace, String srcAbsPath, String destAbsPath, boolean removeExisting) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
335 super.clone(srcWorkspace, srcAbsPath, destAbsPath, removeExisting);
336
337 }
338
339 }
340
341
342
343
344 public class LastUpdateSessionWrapper extends ContentDecoratorSessionWrapper<LastUpdateContentDecorator> implements Session {
345
346 public LastUpdateSessionWrapper(Session session, LastUpdateContentDecorator contentDecorator) {
347 super(session, contentDecorator);
348 }
349
350 @Override
351 public void move(String srcAbsPath, String destAbsPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, RepositoryException {
352 super.move(srcAbsPath, destAbsPath);
353
354 updateLastModified(super.getWrappedSession(), srcAbsPath, true);
355 }
356
357 @Override
358 public void save() throws AccessDeniedException, ItemExistsException, ReferentialIntegrityException, ConstraintViolationException, InvalidItemStateException, VersionException, LockException, NoSuchNodeTypeException, RepositoryException {
359 String workspaceName = wrapped.getWorkspace().getName();
360 try {
361 log.debug("saving session: " + wrapped.toString() + "::" + workspaceName + "::sys:" + MgnlContext.isSystemInstance() + "::" + Thread.currentThread().getName());
362 super.save();
363 if (MgnlContext.isSystemInstance()) {
364 isSysSessionDirty = false;
365 }
366 } catch (InvalidItemStateException e) {
367 log.error("Failed to update LUD for session: " + wrapped.toString() + "::" + workspaceName, e);
368 throw e;
369 }
370 if (isSysSessionDirty) {
371 Session sysSession = MgnlContext.getSystemContext().getJCRSession(workspaceName);
372 if (sysSession instanceof DelegateSessionWrapper) {
373 sysSession = ((DelegateSessionWrapper) sysSession).deepUnwrap(LastUpdateSessionWrapper.class);
374 }
375 if (!wrapped.toString().equals(sysSession.toString())) {
376 log.debug("Forcing saving of : " + sysSession.toString() + "::" + workspaceName);
377 sysSession.save();
378 }
379 isSysSessionDirty = false;
380 }
381 }
382
383 }
384
385 @Override
386 public Session wrapSession(Session session) {
387
388 return new LastUpdateSessionWrapper(session, this);
389 }
390
391 @Override
392 public Workspace wrapWorkspace(Workspace workspace) {
393 return new LastUpdateWorkspaceWrapper(workspace, this);
394 }
395
396 @Override
397 public Node wrapNode(Node node) {
398 return new LastUpdateNodeWrapper(node, this);
399 }
400
401 @Override
402 public Property wrapProperty(Property property) {
403 return new LastUpdatePropertyWrapper(property, this);
404 }
405
406 void updateLastModified(final Session session, final String destAbsPath, final boolean recursiveDown) throws RepositoryException, PathNotFoundException {
407 this.updateLastModified(session.getWorkspace().getName(), destAbsPath, recursiveDown);
408 }
409
410 void updateLastModified(final String workspaceName, final String destAbsPath, final boolean recursiveDown) throws RepositoryException, PathNotFoundException {
411
412 if ("/".equals(destAbsPath) && !recursiveDown) {
413
414 return;
415 }
416 User user = MgnlContext.getUser();
417 final String username = user == null ? "not available" : user.getName();
418
419 final Calendar updateDate = Calendar.getInstance();
420
421
422 MgnlContext.doInSystemContext(new ChangeLastUpdateDateOp(workspaceName, username, destAbsPath, updateDate, recursiveDown));
423
424 }
425
426 void updateLastModified(String workspaceName, String destAbsPath) throws RepositoryException, PathNotFoundException {
427 updateLastModified(workspaceName, destAbsPath, false);
428 }
429
430 void updateLastModified(Session session, String destAbsPath) throws RepositoryException, PathNotFoundException {
431 updateLastModified(session, destAbsPath, false);
432 }
433
434 void updateLastModifiedProperty(String workspaceName, String propertyName, String parentPath) throws RepositoryException {
435 if (shouldIgnoreUpdate(propertyName)) {
436 return;
437 }
438 updateLastModified(workspaceName, parentPath);
439 }
440
441
442
443
444 protected boolean shouldIgnoreUpdate(final String propertyName) {
445
446 return propertyName.startsWith(NodeTypes.JCR_PREFIX) || (propertyName.startsWith(NodeTypes.MGNL_PREFIX) && !NodeTypes.Renderable.TEMPLATE.equals(propertyName));
447 }
448 }