]> git.argeo.org Git - gpl/argeo-jcr.git/blob - CmsWorkspaceIndexer.java
69b98dc3a932a4457f07679b53bfec2e74c2bd57
[gpl/argeo-jcr.git] / CmsWorkspaceIndexer.java
1 package org.argeo.cms.jcr.internal;
2
3 import java.util.GregorianCalendar;
4 import java.util.concurrent.LinkedBlockingDeque;
5 import java.util.concurrent.atomic.AtomicBoolean;
6
7 import javax.jcr.Node;
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;
18
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;
23
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);
27
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";
35
36 private String cn;
37 private String workspaceName;
38 private RepositoryImpl repositoryImpl;
39 private Session session;
40 private VersionManager versionManager;
41
42 private LinkedBlockingDeque<Event> toProcess = new LinkedBlockingDeque<>();
43 private IndexingThread indexingThread;
44 private AtomicBoolean stopping = new AtomicBoolean(false);
45
46 public CmsWorkspaceIndexer(RepositoryImpl repositoryImpl, String cn, String workspaceName)
47 throws RepositoryException {
48 this.cn = cn;
49 this.workspaceName = workspaceName;
50 this.repositoryImpl = repositoryImpl;
51 }
52
53 public void init() {
54 session = KernelUtils.openAdminSession(repositoryImpl, workspaceName);
55 try {
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();
60
61 indexingThread = new IndexingThread();
62 indexingThread.start();
63 } catch (RepositoryException e1) {
64 throw new IllegalStateException(e1);
65 }
66 }
67
68 public void destroy() {
69 stopping.set(true);
70 indexingThread.interrupt();
71 // TODO make it configurable
72 try {
73 indexingThread.join(10 * 60 * 1000);
74 } catch (InterruptedException e1) {
75 log.warn("Indexing thread interrupted. Will log out session.");
76 }
77
78 try {
79 session.getWorkspace().getObservationManager().removeEventListener(this);
80 } catch (RepositoryException e) {
81 if (log.isTraceEnabled())
82 log.warn("Cannot unregistered JCR event listener", e);
83 } finally {
84 JcrUtils.logoutQuietly(session);
85 }
86 }
87
88 private synchronized void processEvents(EventIterator events) {
89 long begin = System.currentTimeMillis();
90 long count = 0;
91 while (events.hasNext()) {
92 Event event = events.nextEvent();
93 try {
94 toProcess.put(event);
95 } catch (InterruptedException e) {
96 e.printStackTrace();
97 }
98 // processEvent(event);
99 count++;
100 }
101 long duration = System.currentTimeMillis() - begin;
102 if (log.isTraceEnabled())
103 log.trace("Processed " + count + " events in " + duration + " ms");
104 notifyAll();
105 }
106
107 protected synchronized void processEvent(Event event) {
108 try {
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());
125 session.save();
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);
132 // session.save();
133 // Property property = node.getProperty(Property.JCR_DATA);
134 // String etag = toEtag(property.getValue());
135 // node.setProperty(JCR_ETAG, etag);
136 // session.save();
137 }
138 // setLastModifiedRecursive(parentNode, event);
139 // session.save();
140 // if (log.isTraceEnabled())
141 // log.trace("ETag and last modified added to new " + node);
142 }
143
144 // if (node.isNodeType(NodeType.NT_FOLDER)) {
145 // setLastModifiedRecursive(node, event);
146 // session.save();
147 // if (log.isTraceEnabled())
148 // log.trace("Last modified added to new " + node);
149 // }
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))
154 return;
155 // if (propertyName.equals(JCR_LAST_MODIFIED))
156 // return;
157 // if (propertyName.equals(JCR_LAST_MODIFIED_BY))
158 // return;
159 // if (propertyName.equals(JCR_MIXIN_TYPES))
160 // return;
161 // if (propertyName.equals(JCR_ETAG))
162 // return;
163
164 if (log.isTraceEnabled())
165 log.trace("PROPERTY_CHANGED " + eventPath);
166
167 if (!session.propertyExists(eventPath))
168 return;
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);
179 }
180 if (log.isTraceEnabled())
181 log.trace("ETag and last modified updated for " + node);
182 }
183 // setLastModified(node, event);
184 // session.save();
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
191 return;
192 if (log.isTraceEnabled())
193 log.trace("NODE_REMOVED " + eventPath);
194 // String parentPath = JcrUtils.parentPath(removeNodePath);
195 // session.refresh(true);
196 // setLastModified(parentPath, event);
197 // session.save();
198 if (log.isTraceEnabled())
199 log.trace("Last modified updated for parents of removed " + removeNodePath);
200 }
201 } catch (Exception e) {
202 if (log.isTraceEnabled())
203 log.warn("Cannot process event " + event, e);
204 } finally {
205 // try {
206 // session.refresh(true);
207 // if (session.hasPendingChanges())
208 // session.save();
209 //// session.refresh(false);
210 // } catch (RepositoryException e) {
211 // if (log.isTraceEnabled())
212 // log.warn("Cannot refresh JCR session", e);
213 // }
214 }
215
216 }
217
218 private String extractItemName(String path) {
219 if (path == null || path.length() <= 1)
220 return null;
221 int lastIndex = path.lastIndexOf('/');
222 if (lastIndex >= 0) {
223 return path.substring(lastIndex + 1);
224 } else {
225 return path;
226 }
227 }
228
229 @Override
230 public void onEvent(EventIterator events) {
231 processEvents(events);
232 // Runnable toRun = new Runnable() {
233 //
234 // @Override
235 // public void run() {
236 // processEvents(events);
237 // }
238 // };
239 // Future<?> future = Activator.getInternalExecutorService().submit(toRun);
240 // try {
241 // // make the call synchronous
242 // future.get(60, TimeUnit.SECONDS);
243 // } catch (TimeoutException | ExecutionException | InterruptedException e) {
244 // // silent
245 // }
246 }
247
248 static String toEtag(Value v) {
249 if (v instanceof JackrabbitValue) {
250 JackrabbitValue value = (JackrabbitValue) v;
251 return '\"' + value.getContentIdentity() + '\"';
252 } else {
253 return null;
254 }
255
256 }
257
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);
265 }
266
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);
272 }
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);
277 }
278
279 }
280
281 // end condition
282 if (node.getDepth() == 0) {
283 // try {
284 // node.getSession().save();
285 // } catch (RepositoryException e) {
286 // log.warn("Cannot index workspace", e);
287 // }
288 return;
289 } else {
290 Node parent = node.getParent();
291 setLastModifiedRecursive(parent, event);
292 }
293 }
294
295 /**
296 * Recursively set the last updated time on parents. Useful to use paths when
297 * dealing with deletions.
298 */
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);
304 } else {
305 setLastModifiedRecursive(JcrUtils.parentPath(path), event);
306 }
307 }
308
309 @Override
310 public String toString() {
311 return "Indexer for workspace " + workspaceName + " of repository " + cn;
312 }
313
314 class IndexingThread extends Thread {
315
316 public IndexingThread() {
317 super(CmsWorkspaceIndexer.this.toString());
318 // TODO Auto-generated constructor stub
319 }
320
321 @Override
322 public void run() {
323 life: while (session != null && session.isLive()) {
324 try {
325 Event nextEvent = toProcess.take();
326 processEvent(nextEvent);
327 } catch (InterruptedException e) {
328 // silent
329 interrupted();
330 }
331
332 if (stopping.get() && toProcess.isEmpty()) {
333 break life;
334 }
335 }
336 if (log.isDebugEnabled())
337 log.debug(CmsWorkspaceIndexer.this.toString() + " has shut down.");
338 }
339
340 }
341
342 }