1 package org
.argeo
.cms
.internal
.kernel
;
3 import java
.util
.GregorianCalendar
;
4 import java
.util
.concurrent
.ExecutionException
;
5 import java
.util
.concurrent
.Future
;
6 import java
.util
.concurrent
.TimeUnit
;
7 import java
.util
.concurrent
.TimeoutException
;
10 import javax
.jcr
.Property
;
11 import javax
.jcr
.PropertyType
;
12 import javax
.jcr
.RepositoryException
;
13 import javax
.jcr
.Session
;
14 import javax
.jcr
.Value
;
15 import javax
.jcr
.nodetype
.NodeType
;
16 import javax
.jcr
.observation
.Event
;
17 import javax
.jcr
.observation
.EventIterator
;
18 import javax
.jcr
.observation
.EventListener
;
19 import javax
.jcr
.version
.VersionManager
;
21 import org
.apache
.commons
.logging
.Log
;
22 import org
.apache
.commons
.logging
.LogFactory
;
23 import org
.apache
.jackrabbit
.api
.JackrabbitValue
;
24 import org
.apache
.jackrabbit
.core
.RepositoryImpl
;
25 import org
.argeo
.jcr
.JcrUtils
;
27 /** Ensure consistency of files, folder and last modified nodes. */
28 class CmsWorkspaceIndexer
implements EventListener
{
29 private final static Log log
= LogFactory
.getLog(CmsWorkspaceIndexer
.class);
31 // private final static String MIX_ETAG = "mix:etag";
32 private final static String JCR_ETAG
= "jcr:etag";
33 private final static String JCR_LAST_MODIFIED
= "jcr:lastModified";
34 private final static String JCR_LAST_MODIFIED_BY
= "jcr:lastModifiedBy";
35 private final static String JCR_DATA
= "jcr:data";
38 private String workspaceName
;
39 private RepositoryImpl repositoryImpl
;
40 private Session session
;
41 private VersionManager versionManager
;
43 public CmsWorkspaceIndexer(RepositoryImpl repositoryImpl
, String cn
, String workspaceName
)
44 throws RepositoryException
{
46 this.workspaceName
= workspaceName
;
47 this.repositoryImpl
= repositoryImpl
;
51 session
= KernelUtils
.openAdminSession(repositoryImpl
, workspaceName
);
53 String
[] nodeTypes
= { NodeType
.NT_FILE
, NodeType
.MIX_LAST_MODIFIED
};
54 session
.getWorkspace().getObservationManager().addEventListener(this,
55 Event
.NODE_ADDED
| Event
.NODE_REMOVED
| Event
.PROPERTY_CHANGED
, "/", true, null, nodeTypes
, true);
56 versionManager
= session
.getWorkspace().getVersionManager();
57 } catch (RepositoryException e1
) {
58 throw new IllegalStateException(e1
);
62 public void destroy() {
64 session
.getWorkspace().getObservationManager().removeEventListener(this);
65 } catch (RepositoryException e
) {
66 if (log
.isTraceEnabled())
67 log
.warn("Cannot unregistered JCR event listener", e
);
69 JcrUtils
.logoutQuietly(session
);
73 private synchronized void processEvents(EventIterator events
) {
74 while (events
.hasNext()) {
75 Event event
= events
.nextEvent();
81 protected synchronized void processEvent(Event event
) {
83 if (event
.getType() == Event
.NODE_ADDED
) {
84 if (!versionManager
.isCheckedOut(event
.getPath()))
85 return;// ignore checked-in nodes
86 session
.refresh(true);
87 Node node
= session
.getNode(event
.getPath());
88 if (node
.getParent().isNodeType(NodeType
.NT_FILE
)) {
89 if (node
.isNodeType(NodeType
.NT_UNSTRUCTURED
)) {
90 if (!node
.isNodeType(NodeType
.MIX_LAST_MODIFIED
))
91 node
.addMixin(NodeType
.MIX_LAST_MODIFIED
);
92 Property property
= node
.getProperty(Property
.JCR_DATA
);
93 String etag
= toEtag(property
.getValue());
94 node
.setProperty(JCR_ETAG
, etag
);
95 } else if (node
.isNodeType(NodeType
.NT_RESOURCE
)) {
96 // if (!node.isNodeType(MIX_ETAG))
97 // node.addMixin(MIX_ETAG);
99 // Property property = node.getProperty(Property.JCR_DATA);
100 // String etag = toEtag(property.getValue());
101 // node.setProperty(JCR_ETAG, etag);
104 setLastModified(node
.getParent(), event
);
106 if (log
.isTraceEnabled())
107 log
.trace("ETag and last modified added to new " + node
);
109 } else if (event
.getType() == Event
.PROPERTY_CHANGED
) {
110 if (!session
.propertyExists(event
.getPath()))
112 session
.refresh(true);
113 Property property
= session
.getProperty(event
.getPath());
114 String propertyName
= property
.getName();
115 // skip if last modified properties are explicitly set
116 if (propertyName
.equals(JCR_LAST_MODIFIED
))
118 if (propertyName
.equals(JCR_LAST_MODIFIED_BY
))
120 Node node
= property
.getParent();
121 if (property
.getType() == PropertyType
.BINARY
&& propertyName
.equals(JCR_DATA
)
122 && node
.isNodeType(NodeType
.NT_UNSTRUCTURED
)) {
123 String etag
= toEtag(property
.getValue());
124 node
.setProperty(JCR_ETAG
, etag
);
126 setLastModified(node
, event
);
128 if (log
.isTraceEnabled())
129 log
.trace("ETag and last modified updated for " + node
);
130 } else if (event
.getType() == Event
.NODE_REMOVED
) {
131 String removeNodePath
= event
.getPath();
132 String parentPath
= JcrUtils
.parentPath(removeNodePath
);
133 session
.refresh(true);
134 setLastModified(parentPath
, event
);
136 if (log
.isTraceEnabled())
137 log
.trace("Last modified updated for parents of removed " + removeNodePath
);
139 } catch (Exception e
) {
140 if (log
.isTraceEnabled())
141 log
.warn("Cannot process event " + event
, e
);
144 // session.refresh(true);
145 // if (session.hasPendingChanges())
147 //// session.refresh(false);
148 // } catch (RepositoryException e) {
149 // if (log.isTraceEnabled())
150 // log.warn("Cannot refresh JCR session", e);
157 public void onEvent(EventIterator events
) {
158 Runnable toRun
= new Runnable() {
162 processEvents(events
);
165 Future
<?
> future
= Activator
.getInternalExecutorService().submit(toRun
);
167 // make the call synchronous
168 future
.get(60, TimeUnit
.SECONDS
);
169 } catch (TimeoutException
| ExecutionException
| InterruptedException e
) {
174 static String
toEtag(Value v
) {
175 if (v
instanceof JackrabbitValue
) {
176 JackrabbitValue value
= (JackrabbitValue
) v
;
177 return '\"' + value
.getContentIdentity() + '\"';
184 /** Recursively set the last updated time on parents. */
185 protected synchronized void setLastModified(Node node
, Event event
) throws RepositoryException
{
186 if (versionManager
.isCheckedOut(node
.getPath())) {
187 GregorianCalendar calendar
= new GregorianCalendar();
188 calendar
.setTimeInMillis(event
.getDate());
189 if (node
.isNodeType(NodeType
.MIX_LAST_MODIFIED
)) {
190 node
.setProperty(Property
.JCR_LAST_MODIFIED
, calendar
);
191 node
.setProperty(Property
.JCR_LAST_MODIFIED_BY
, event
.getUserID());
193 if (node
.isNodeType(NodeType
.NT_FOLDER
) && !node
.isNodeType(NodeType
.MIX_LAST_MODIFIED
)) {
194 node
.addMixin(NodeType
.MIX_LAST_MODIFIED
);
198 node
.getSession().save();
199 } catch (RepositoryException e
) {
200 // fail silently and keep recursing
203 if (node
.getDepth() == 0)
205 Node parent
= node
.getParent();
206 setLastModified(parent
, event
);
210 * Recursively set the last updated time on parents. Useful to use paths when
211 * dealing with deletions.
213 protected synchronized void setLastModified(String path
, Event event
) throws RepositoryException
{
214 // root node will always exist, so end condition is delegated to the other
215 // recursive setLastModified method
216 if (session
.nodeExists(path
)) {
217 setLastModified(session
.getNode(path
), event
);
219 setLastModified(JcrUtils
.parentPath(path
), event
);
224 public String
toString() {
225 return "Indexer for workspace " + workspaceName
+ " of repository " + cn
;