1 package org
.argeo
.cms
.jcr
.internal
;
3 import java
.util
.GregorianCalendar
;
4 import java
.util
.concurrent
.LinkedBlockingDeque
;
5 import java
.util
.concurrent
.atomic
.AtomicBoolean
;
8 import javax
.jcr
.Property
;
9 import javax
.jcr
.PropertyType
;
10 import javax
.jcr
.RepositoryException
;
11 import javax
.jcr
.Session
;
12 import javax
.jcr
.Value
;
13 import javax
.jcr
.nodetype
.NodeType
;
14 import javax
.jcr
.observation
.Event
;
15 import javax
.jcr
.observation
.EventIterator
;
16 import javax
.jcr
.observation
.EventListener
;
17 import javax
.jcr
.version
.VersionManager
;
19 import org
.apache
.jackrabbit
.api
.JackrabbitValue
;
20 import org
.apache
.jackrabbit
.core
.RepositoryImpl
;
21 import org
.argeo
.api
.cms
.CmsLog
;
22 import org
.argeo
.jcr
.JcrUtils
;
24 /** Ensure consistency of files, folder and last modified nodes. */
25 class CmsWorkspaceIndexer
implements EventListener
{
26 private final static CmsLog log
= CmsLog
.getLog(CmsWorkspaceIndexer
.class);
28 // private final static String MIX_ETAG = "mix:etag";
29 private final static String JCR_ETAG
= "jcr:etag";
30 // private final static String JCR_LAST_MODIFIED = "jcr:lastModified";
31 // private final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
32 // private final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
33 private final static String JCR_DATA
= "jcr:data";
34 private final static String JCR_CONTENT
= "jcr:data";
37 private String workspaceName
;
38 private RepositoryImpl repositoryImpl
;
39 private Session session
;
40 private VersionManager versionManager
;
42 private LinkedBlockingDeque
<Event
> toProcess
= new LinkedBlockingDeque
<>();
43 private IndexingThread indexingThread
;
44 private AtomicBoolean stopping
= new AtomicBoolean(false);
46 public CmsWorkspaceIndexer(RepositoryImpl repositoryImpl
, String cn
, String workspaceName
)
47 throws RepositoryException
{
49 this.workspaceName
= workspaceName
;
50 this.repositoryImpl
= repositoryImpl
;
54 session
= KernelUtils
.openAdminSession(repositoryImpl
, workspaceName
);
56 String
[] nodeTypes
= { NodeType
.NT_FILE
, NodeType
.MIX_LAST_MODIFIED
};
57 session
.getWorkspace().getObservationManager().addEventListener(this,
58 Event
.NODE_ADDED
| Event
.PROPERTY_CHANGED
, "/", true, null, nodeTypes
, true);
59 versionManager
= session
.getWorkspace().getVersionManager();
61 indexingThread
= new IndexingThread();
62 indexingThread
.start();
63 } catch (RepositoryException e1
) {
64 throw new IllegalStateException(e1
);
68 public void destroy() {
70 indexingThread
.interrupt();
71 // TODO make it configurable
73 indexingThread
.join(10 * 60 * 1000);
74 } catch (InterruptedException e1
) {
75 log
.warn("Indexing thread interrupted. Will log out session.");
79 session
.getWorkspace().getObservationManager().removeEventListener(this);
80 } catch (RepositoryException e
) {
81 if (log
.isTraceEnabled())
82 log
.warn("Cannot unregistered JCR event listener", e
);
84 JcrUtils
.logoutQuietly(session
);
88 private synchronized void processEvents(EventIterator events
) {
89 long begin
= System
.currentTimeMillis();
91 while (events
.hasNext()) {
92 Event event
= events
.nextEvent();
95 } catch (InterruptedException e
) {
98 // processEvent(event);
101 long duration
= System
.currentTimeMillis() - begin
;
102 if (log
.isTraceEnabled())
103 log
.trace("Processed " + count
+ " events in " + duration
+ " ms");
107 protected synchronized void processEvent(Event event
) {
109 String eventPath
= event
.getPath();
110 if (event
.getType() == Event
.NODE_ADDED
) {
111 if (!versionManager
.isCheckedOut(eventPath
))
112 return;// ignore checked-in nodes
113 if (log
.isTraceEnabled())
114 log
.trace("NODE_ADDED " + eventPath
);
115 // session.refresh(true);
116 session
.refresh(false);
117 Node node
= session
.getNode(eventPath
);
118 Node parentNode
= node
.getParent();
119 if (parentNode
.isNodeType(NodeType
.NT_FILE
)) {
120 if (node
.isNodeType(NodeType
.NT_UNSTRUCTURED
)) {
121 if (!node
.isNodeType(NodeType
.MIX_LAST_MODIFIED
))
122 node
.addMixin(NodeType
.MIX_LAST_MODIFIED
);
123 Property property
= node
.getProperty(Property
.JCR_DATA
);
124 String etag
= toEtag(property
.getValue());
126 node
.setProperty(JCR_ETAG
, etag
);
127 if (log
.isTraceEnabled())
128 log
.trace("ETag and last modified added to new " + node
);
129 } else if (node
.isNodeType(NodeType
.NT_RESOURCE
)) {
130 // if (!node.isNodeType(MIX_ETAG))
131 // node.addMixin(MIX_ETAG);
133 // Property property = node.getProperty(Property.JCR_DATA);
134 // String etag = toEtag(property.getValue());
135 // node.setProperty(JCR_ETAG, etag);
138 // setLastModifiedRecursive(parentNode, event);
140 // if (log.isTraceEnabled())
141 // log.trace("ETag and last modified added to new " + node);
144 // if (node.isNodeType(NodeType.NT_FOLDER)) {
145 // setLastModifiedRecursive(node, event);
147 // if (log.isTraceEnabled())
148 // log.trace("Last modified added to new " + node);
150 } else if (event
.getType() == Event
.PROPERTY_CHANGED
) {
151 String propertyName
= extractItemName(eventPath
);
152 // skip if last modified properties are explicitly set
153 if (!propertyName
.equals(JCR_DATA
))
155 // if (propertyName.equals(JCR_LAST_MODIFIED))
157 // if (propertyName.equals(JCR_LAST_MODIFIED_BY))
159 // if (propertyName.equals(JCR_MIXIN_TYPES))
161 // if (propertyName.equals(JCR_ETAG))
164 if (log
.isTraceEnabled())
165 log
.trace("PROPERTY_CHANGED " + eventPath
);
167 if (!session
.propertyExists(eventPath
))
169 session
.refresh(false);
170 Property property
= session
.getProperty(eventPath
);
171 Node node
= property
.getParent();
172 if (property
.getType() == PropertyType
.BINARY
&& propertyName
.equals(JCR_DATA
)
173 && node
.isNodeType(NodeType
.NT_UNSTRUCTURED
)) {
174 String etag
= toEtag(property
.getValue());
175 node
.setProperty(JCR_ETAG
, etag
);
176 Node parentNode
= node
.getParent();
177 if (parentNode
.isNodeType(NodeType
.MIX_LAST_MODIFIED
)) {
178 setLastModified(parentNode
, event
);
180 if (log
.isTraceEnabled())
181 log
.trace("ETag and last modified updated for " + node
);
183 // setLastModified(node, event);
185 // if (log.isTraceEnabled())
186 // log.trace("ETag and last modified updated for " + node);
187 } else if (event
.getType() == Event
.NODE_REMOVED
) {
188 String removeNodePath
= eventPath
;
189 String nodeName
= extractItemName(eventPath
);
190 if (JCR_CONTENT
.equals(nodeName
)) // parent is a file, deleted anyhow
192 if (log
.isTraceEnabled())
193 log
.trace("NODE_REMOVED " + eventPath
);
194 // String parentPath = JcrUtils.parentPath(removeNodePath);
195 // session.refresh(true);
196 // setLastModified(parentPath, event);
198 if (log
.isTraceEnabled())
199 log
.trace("Last modified updated for parents of removed " + removeNodePath
);
201 } catch (Exception e
) {
202 if (log
.isTraceEnabled())
203 log
.warn("Cannot process event " + event
, e
);
206 // session.refresh(true);
207 // if (session.hasPendingChanges())
209 //// session.refresh(false);
210 // } catch (RepositoryException e) {
211 // if (log.isTraceEnabled())
212 // log.warn("Cannot refresh JCR session", e);
218 private String
extractItemName(String path
) {
219 if (path
== null || path
.length() <= 1)
221 int lastIndex
= path
.lastIndexOf('/');
222 if (lastIndex
>= 0) {
223 return path
.substring(lastIndex
+ 1);
230 public void onEvent(EventIterator events
) {
231 processEvents(events
);
232 // Runnable toRun = new Runnable() {
235 // public void run() {
236 // processEvents(events);
239 // Future<?> future = Activator.getInternalExecutorService().submit(toRun);
241 // // make the call synchronous
242 // future.get(60, TimeUnit.SECONDS);
243 // } catch (TimeoutException | ExecutionException | InterruptedException e) {
248 static String
toEtag(Value v
) {
249 if (v
instanceof JackrabbitValue
) {
250 JackrabbitValue value
= (JackrabbitValue
) v
;
251 return '\"' + value
.getContentIdentity() + '\"';
258 protected synchronized void setLastModified(Node node
, Event event
) throws RepositoryException
{
259 GregorianCalendar calendar
= new GregorianCalendar();
260 calendar
.setTimeInMillis(event
.getDate());
261 node
.setProperty(Property
.JCR_LAST_MODIFIED
, calendar
);
262 node
.setProperty(Property
.JCR_LAST_MODIFIED_BY
, event
.getUserID());
263 if (log
.isTraceEnabled())
264 log
.trace("Last modified set on " + node
);
267 /** Recursively set the last updated time on parents. */
268 protected synchronized void setLastModifiedRecursive(Node node
, Event event
) throws RepositoryException
{
269 if (versionManager
.isCheckedOut(node
.getPath())) {
270 if (node
.isNodeType(NodeType
.MIX_LAST_MODIFIED
)) {
271 setLastModified(node
, event
);
273 if (node
.isNodeType(NodeType
.NT_FOLDER
) && !node
.isNodeType(NodeType
.MIX_LAST_MODIFIED
)) {
274 node
.addMixin(NodeType
.MIX_LAST_MODIFIED
);
275 if (log
.isTraceEnabled())
276 log
.trace("Last modified mix-in added to " + node
);
282 if (node
.getDepth() == 0) {
284 // node.getSession().save();
285 // } catch (RepositoryException e) {
286 // log.warn("Cannot index workspace", e);
290 Node parent
= node
.getParent();
291 setLastModifiedRecursive(parent
, event
);
296 * Recursively set the last updated time on parents. Useful to use paths when
297 * dealing with deletions.
299 protected synchronized void setLastModifiedRecursive(String path
, Event event
) throws RepositoryException
{
300 // root node will always exist, so end condition is delegated to the other
301 // recursive setLastModified method
302 if (session
.nodeExists(path
)) {
303 setLastModifiedRecursive(session
.getNode(path
), event
);
305 setLastModifiedRecursive(JcrUtils
.parentPath(path
), event
);
310 public String
toString() {
311 return "Indexer for workspace " + workspaceName
+ " of repository " + cn
;
314 class IndexingThread
extends Thread
{
316 public IndexingThread() {
317 super(CmsWorkspaceIndexer
.this.toString());
318 // TODO Auto-generated constructor stub
323 life
: while (session
!= null && session
.isLive()) {
325 Event nextEvent
= toProcess
.take();
326 processEvent(nextEvent
);
327 } catch (InterruptedException e
) {
332 if (stopping
.get() && toProcess
.isEmpty()) {
336 if (log
.isDebugEnabled())
337 log
.debug(CmsWorkspaceIndexer
.this.toString() + " has shut down.");