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 (skipTypeInWorkspace(workspaceName, 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 private boolean skipTypeInWorkspace(String workspaceName, String name) {
209 if (RepositoryConstants.USER_ROLES.equals(workspaceName)) {
210 return !(NodeTypes.Role.NAME.equals(name) || NodeTypes.Folder.NAME.equals(name));
211 }
212 if (RepositoryConstants.WEBSITE.equals(workspaceName)) {
213 return !NodeTypes.Page.NAME.equals(name);
214 }
215 return false;
216 }
217 }
218
219
220
221
222 public class LastUpdatePropertyWrapper extends ContentDecoratorPropertyWrapper<LastUpdateContentDecorator> implements Property {
223
224 public LastUpdatePropertyWrapper(Property property, LastUpdateContentDecorator contentDecorator) {
225 super(property, contentDecorator);
226 }
227
228 @Override
229 public void setValue(BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
230 super.setValue(value);
231 this.updateLastModifiedProperty();
232 }
233
234 @Override
235 public void setValue(Binary value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
236 super.setValue(value);
237 this.updateLastModifiedProperty();
238 }
239
240 @Override
241 public void setValue(boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
242 super.setValue(value);
243 this.updateLastModifiedProperty();
244 }
245
246 @Override
247 public void setValue(Calendar value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
248 super.setValue(value);
249 this.updateLastModifiedProperty();
250 }
251
252 @Override
253 public void setValue(double value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
254 super.setValue(value);
255 this.updateLastModifiedProperty();
256 }
257
258 @Override
259 public void setValue(InputStream value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
260 super.setValue(value);
261 this.updateLastModifiedProperty();
262 }
263
264 @Override
265 public void setValue(long value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
266 super.setValue(value);
267 this.updateLastModifiedProperty();
268 }
269
270 @Override
271 public void setValue(Node value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
272 super.setValue(value);
273 this.updateLastModifiedProperty();
274 }
275
276 @Override
277 public void setValue(String value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
278 super.setValue(value);
279 this.updateLastModifiedProperty();
280 }
281
282 @Override
283 public void setValue(String[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
284 super.setValue(values);
285 this.updateLastModifiedProperty();
286 }
287
288 @Override
289 public void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
290 super.setValue(value);
291 this.updateLastModifiedProperty();
292 }
293
294 @Override
295 public void setValue(Value[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
296 super.setValue(values);
297 this.updateLastModifiedProperty();
298 }
299
300 @Override
301 public void remove() throws VersionException, LockException, ConstraintViolationException, AccessDeniedException, RepositoryException {
302 Node parent = this.getParent();
303 String propertyName = this.getName();
304 super.remove();
305 if (shouldIgnoreUpdate(propertyName)) {
306 return;
307 }
308 updateLastModified(parent.getSession(), parent.getPath());
309 }
310
311 private void updateLastModifiedProperty() throws RepositoryException {
312 LastUpdateContentDecorator.this.updateLastModifiedProperty(getSession().getWorkspace().getName(), this.getName(), this.getParent().getPath());
313 }
314 }
315
316
317
318
319 public class LastUpdateWorkspaceWrapper extends ContentDecoratorWorkspaceWrapper implements Workspace {
320
321 protected LastUpdateWorkspaceWrapper(Workspace workspace, ContentDecorator contentDecorator) {
322 super(workspace, contentDecorator);
323 }
324
325 @Override
326 public void move(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
327 super.move(srcAbsPath, destAbsPath);
328 updateLastModified(super.getWrappedWorkspace().getSession(), destAbsPath, true);
329 }
330
331 @Override
332 public void copy(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
333 super.copy(srcAbsPath, destAbsPath);
334 updateLastModified(super.getWrappedWorkspace().getSession(), destAbsPath, true);
335 }
336
337 @Override
338 public void copy(String srcWorkspace, String srcAbsPath, String destAbsPath) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
339 super.copy(srcWorkspace, srcAbsPath, destAbsPath);
340 updateLastModified(super.getWrappedWorkspace().getSession(), destAbsPath, true);
341 }
342
343 @Override
344 public void clone(String srcWorkspace, String srcAbsPath, String destAbsPath, boolean removeExisting) throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
345 super.clone(srcWorkspace, srcAbsPath, destAbsPath, removeExisting);
346
347 }
348
349 }
350
351
352
353
354 public class LastUpdateSessionWrapper extends ContentDecoratorSessionWrapper<LastUpdateContentDecorator> implements Session {
355
356 public LastUpdateSessionWrapper(Session session, LastUpdateContentDecorator contentDecorator) {
357 super(session, contentDecorator);
358 }
359
360 @Override
361 public void move(String srcAbsPath, String destAbsPath) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, RepositoryException {
362 super.move(srcAbsPath, destAbsPath);
363
364 updateLastModified(super.getWrappedSession(), srcAbsPath, true);
365 }
366
367 @Override
368 public void save() throws AccessDeniedException, ItemExistsException, ReferentialIntegrityException, ConstraintViolationException, InvalidItemStateException, VersionException, LockException, NoSuchNodeTypeException, RepositoryException {
369 String workspaceName = wrapped.getWorkspace().getName();
370 try {
371 log.debug("saving session: " + wrapped.toString() + "::" + workspaceName + "::sys:" + MgnlContext.isSystemInstance() + "::" + Thread.currentThread().getName());
372 super.save();
373 if (MgnlContext.isSystemInstance()) {
374 isSysSessionDirty = false;
375 }
376 } catch (InvalidItemStateException e) {
377 log.error("Failed to update LUD for session: " + wrapped.toString() + "::" + workspaceName, e);
378 throw e;
379 }
380 if (isSysSessionDirty) {
381 Session sysSession = MgnlContext.getSystemContext().getJCRSession(workspaceName);
382 if (sysSession instanceof DelegateSessionWrapper) {
383 sysSession = ((DelegateSessionWrapper) sysSession).deepUnwrap(LastUpdateSessionWrapper.class);
384 }
385 if (!wrapped.toString().equals(sysSession.toString())) {
386 log.debug("Forcing saving of : " + sysSession.toString() + "::" + workspaceName);
387 sysSession.save();
388 }
389 isSysSessionDirty = false;
390 }
391 }
392
393 }
394
395 @Override
396 public Session wrapSession(Session session) {
397
398 return new LastUpdateSessionWrapper(session, this);
399 }
400
401 @Override
402 public Workspace wrapWorkspace(Workspace workspace) {
403 return new LastUpdateWorkspaceWrapper(workspace, this);
404 }
405
406 @Override
407 public Node wrapNode(Node node) {
408 return new LastUpdateNodeWrapper(node, this);
409 }
410
411 @Override
412 public Property wrapProperty(Property property) {
413 return new LastUpdatePropertyWrapper(property, this);
414 }
415
416 void updateLastModified(final Session session, final String destAbsPath, final boolean recursiveDown) throws RepositoryException, PathNotFoundException {
417 this.updateLastModified(session.getWorkspace().getName(), destAbsPath, recursiveDown);
418 }
419
420 void updateLastModified(final String workspaceName, final String destAbsPath, final boolean recursiveDown) throws RepositoryException, PathNotFoundException {
421
422 if ("/".equals(destAbsPath) && !recursiveDown) {
423
424 return;
425 }
426 User user = MgnlContext.getUser();
427 final String username = user == null ? "not available" : user.getName();
428
429 final Calendar updateDate = Calendar.getInstance();
430
431
432 MgnlContext.doInSystemContext(new ChangeLastUpdateDateOp(workspaceName, username, destAbsPath, updateDate, recursiveDown));
433
434 }
435
436 void updateLastModified(String workspaceName, String destAbsPath) throws RepositoryException, PathNotFoundException {
437 updateLastModified(workspaceName, destAbsPath, false);
438 }
439
440 void updateLastModified(Session session, String destAbsPath) throws RepositoryException, PathNotFoundException {
441 updateLastModified(session, destAbsPath, false);
442 }
443
444 void updateLastModifiedProperty(String workspaceName, String propertyName, String parentPath) throws RepositoryException {
445 if (shouldIgnoreUpdate(propertyName)) {
446 return;
447 }
448 updateLastModified(workspaceName, parentPath);
449 }
450
451
452
453
454 protected boolean shouldIgnoreUpdate(final String propertyName) {
455
456 return propertyName.startsWith(NodeTypes.JCR_PREFIX) || (propertyName.startsWith(NodeTypes.MGNL_PREFIX) && !NodeTypes.Renderable.TEMPLATE.equals(propertyName));
457 }
458 }