]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java
Work on SNAPSHOTs containers.
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / CmsWorkspaceIndexer.java
1 package org.argeo.cms.internal.kernel;
2
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;
8
9 import javax.jcr.Node;
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;
20
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;
26
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);
30
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";
36
37 private String cn;
38 private String workspaceName;
39 private RepositoryImpl repositoryImpl;
40 private Session session;
41 private VersionManager versionManager;
42
43 public CmsWorkspaceIndexer(RepositoryImpl repositoryImpl, String cn, String workspaceName)
44 throws RepositoryException {
45 this.cn = cn;
46 this.workspaceName = workspaceName;
47 this.repositoryImpl = repositoryImpl;
48 }
49
50 public void init() {
51 session = KernelUtils.openAdminSession(repositoryImpl, workspaceName);
52 try {
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);
59 }
60 }
61
62 public void destroy() {
63 try {
64 session.getWorkspace().getObservationManager().removeEventListener(this);
65 } catch (RepositoryException e) {
66 if (log.isTraceEnabled())
67 log.warn("Cannot unregistered JCR event listener", e);
68 } finally {
69 JcrUtils.logoutQuietly(session);
70 }
71 }
72
73 private synchronized void processEvents(EventIterator events) {
74 while (events.hasNext()) {
75 Event event = events.nextEvent();
76 processEvent(event);
77 }
78 notifyAll();
79 }
80
81 protected synchronized void processEvent(Event event) {
82 try {
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);
98 // session.save();
99 // Property property = node.getProperty(Property.JCR_DATA);
100 // String etag = toEtag(property.getValue());
101 // node.setProperty(JCR_ETAG, etag);
102 // session.save();
103 }
104 setLastModified(node.getParent(), event);
105 session.save();
106 if (log.isTraceEnabled())
107 log.trace("ETag and last modified added to new " + node);
108 }
109 } else if (event.getType() == Event.PROPERTY_CHANGED) {
110 if (!session.propertyExists(event.getPath()))
111 return;
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))
117 return;
118 if (propertyName.equals(JCR_LAST_MODIFIED_BY))
119 return;
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);
125 }
126 setLastModified(node, event);
127 session.save();
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);
135 session.save();
136 if (log.isTraceEnabled())
137 log.trace("Last modified updated for parents of removed " + removeNodePath);
138 }
139 } catch (Exception e) {
140 if (log.isTraceEnabled())
141 log.warn("Cannot process event " + event, e);
142 } finally {
143 // try {
144 // session.refresh(true);
145 // if (session.hasPendingChanges())
146 // session.save();
147 //// session.refresh(false);
148 // } catch (RepositoryException e) {
149 // if (log.isTraceEnabled())
150 // log.warn("Cannot refresh JCR session", e);
151 // }
152 }
153
154 }
155
156 @Override
157 public void onEvent(EventIterator events) {
158 Runnable toRun = new Runnable() {
159
160 @Override
161 public void run() {
162 processEvents(events);
163 }
164 };
165 Future<?> future = Activator.getInternalExecutorService().submit(toRun);
166 try {
167 // make the call synchronous
168 future.get(60, TimeUnit.SECONDS);
169 } catch (TimeoutException | ExecutionException | InterruptedException e) {
170 // silent
171 }
172 }
173
174 static String toEtag(Value v) {
175 if (v instanceof JackrabbitValue) {
176 JackrabbitValue value = (JackrabbitValue) v;
177 return '\"' + value.getContentIdentity() + '\"';
178 } else {
179 return null;
180 }
181
182 }
183
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());
192 }
193 if (node.isNodeType(NodeType.NT_FOLDER) && !node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
194 node.addMixin(NodeType.MIX_LAST_MODIFIED);
195 }
196
197 try {
198 node.getSession().save();
199 } catch (RepositoryException e) {
200 // fail silently and keep recursing
201 }
202 }
203 if (node.getDepth() == 0)
204 return;
205 Node parent = node.getParent();
206 setLastModified(parent, event);
207 }
208
209 /**
210 * Recursively set the last updated time on parents. Useful to use paths when
211 * dealing with deletions.
212 */
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);
218 } else {
219 setLastModified(JcrUtils.parentPath(path), event);
220 }
221 }
222
223 @Override
224 public String toString() {
225 return "Indexer for workspace " + workspaceName + " of repository " + cn;
226 }
227
228 }