]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java
Improve JCR repository lifecycle
[lgpl/argeo-commons.git] / org.argeo.cms.jcr / src / org / argeo / jcr / ThreadBoundJcrSessionFactory.java
1 package org.argeo.jcr;
2
3 import java.lang.reflect.InvocationHandler;
4 import java.lang.reflect.InvocationTargetException;
5 import java.lang.reflect.Method;
6 import java.lang.reflect.Proxy;
7 import java.util.ArrayList;
8 import java.util.Collections;
9 import java.util.HashMap;
10 import java.util.Iterator;
11 import java.util.List;
12 import java.util.Map;
13
14 import javax.jcr.LoginException;
15 import javax.jcr.Repository;
16 import javax.jcr.RepositoryException;
17 import javax.jcr.Session;
18 import javax.jcr.SimpleCredentials;
19
20 import org.argeo.api.cms.CmsLog;
21
22 /** Proxy JCR sessions and attach them to calling threads. */
23 @Deprecated
24 public abstract class ThreadBoundJcrSessionFactory {
25 private final static CmsLog log = CmsLog.getLog(ThreadBoundJcrSessionFactory.class);
26
27 private Repository repository;
28 /** can be injected as list, only used if repository is null */
29 private List<Repository> repositories;
30
31 private ThreadLocal<Session> session = new ThreadLocal<Session>();
32 private final Session proxiedSession;
33 /** If workspace is null, default will be used. */
34 private String workspace = null;
35
36 private String defaultUsername = "demo";
37 private String defaultPassword = "demo";
38 private Boolean forceDefaultCredentials = false;
39
40 private boolean active = true;
41
42 // monitoring
43 private final List<Thread> threads = Collections.synchronizedList(new ArrayList<Thread>());
44 private final Map<Long, Session> activeSessions = Collections.synchronizedMap(new HashMap<Long, Session>());
45 private MonitoringThread monitoringThread;
46
47 public ThreadBoundJcrSessionFactory() {
48 Class<?>[] interfaces = { Session.class };
49 proxiedSession = (Session) Proxy.newProxyInstance(ThreadBoundJcrSessionFactory.class.getClassLoader(),
50 interfaces, new JcrSessionInvocationHandler());
51 }
52
53 /** Logs in to the repository using various strategies. */
54 protected synchronized Session login() {
55 if (!isActive())
56 throw new IllegalStateException("Thread bound session factory inactive");
57
58 // discard session previously attached to this thread
59 Thread thread = Thread.currentThread();
60 if (activeSessions.containsKey(thread.getId())) {
61 Session oldSession = activeSessions.remove(thread.getId());
62 oldSession.logout();
63 session.remove();
64 }
65
66 Session newSession = null;
67 // first try to login without credentials, assuming the underlying login
68 // module will have dealt with authentication (typically using Spring
69 // Security)
70 if (!forceDefaultCredentials)
71 try {
72 newSession = repository().login(workspace);
73 } catch (LoginException e1) {
74 log.warn("Cannot login without credentials: " + e1.getMessage());
75 // invalid credentials, go to the next step
76 } catch (RepositoryException e1) {
77 // other kind of exception, fail
78 throw new JcrException("Cannot log in to repository", e1);
79 }
80
81 // log using default username / password (useful for testing purposes)
82 if (newSession == null)
83 try {
84 SimpleCredentials sc = new SimpleCredentials(defaultUsername, defaultPassword.toCharArray());
85 newSession = repository().login(sc, workspace);
86 } catch (RepositoryException e) {
87 throw new JcrException("Cannot log in to repository", e);
88 }
89
90 session.set(newSession);
91 // Log and monitor new session
92 if (log.isTraceEnabled())
93 log.trace("Logged in to JCR session " + newSession + "; userId=" + newSession.getUserID());
94
95 // monitoring
96 activeSessions.put(thread.getId(), newSession);
97 threads.add(thread);
98 return newSession;
99 }
100
101 public Object getObject() {
102 return proxiedSession;
103 }
104
105 public void init() throws Exception {
106 // log.error("SHOULD NOT BE USED ANYMORE");
107 monitoringThread = new MonitoringThread();
108 monitoringThread.start();
109 }
110
111 public void dispose() throws Exception {
112 // if (activeSessions.size() == 0)
113 // return;
114
115 if (log.isTraceEnabled())
116 log.trace("Cleaning up " + activeSessions.size() + " active JCR sessions...");
117
118 deactivate();
119 for (Session sess : activeSessions.values()) {
120 JcrUtils.logoutQuietly(sess);
121 }
122 activeSessions.clear();
123 }
124
125 protected Boolean isActive() {
126 return active;
127 }
128
129 protected synchronized void deactivate() {
130 active = false;
131 notifyAll();
132 }
133
134 protected synchronized void removeSession(Thread thread) {
135 if (!isActive())
136 return;
137 activeSessions.remove(thread.getId());
138 threads.remove(thread);
139 }
140
141 protected synchronized void cleanDeadThreads() {
142 if (!isActive())
143 return;
144 Iterator<Thread> it = threads.iterator();
145 while (it.hasNext()) {
146 Thread thread = it.next();
147 if (!thread.isAlive() && isActive()) {
148 if (activeSessions.containsKey(thread.getId())) {
149 Session session = activeSessions.get(thread.getId());
150 activeSessions.remove(thread.getId());
151 session.logout();
152 if (log.isTraceEnabled())
153 log.trace("Cleaned up JCR session (userID=" + session.getUserID() + ") from dead thread "
154 + thread.getId());
155 }
156 it.remove();
157 }
158 }
159 try {
160 wait(1000);
161 } catch (InterruptedException e) {
162 // silent
163 }
164 }
165
166 public Class<? extends Session> getObjectType() {
167 return Session.class;
168 }
169
170 public boolean isSingleton() {
171 return true;
172 }
173
174 /**
175 * Called before a method is actually called, allowing to check the session or
176 * re-login it (e.g. if authentication has changed). The default implementation
177 * returns the session.
178 */
179 protected Session preCall(Session session) {
180 return session;
181 }
182
183 protected Repository repository() {
184 if (repository != null)
185 return repository;
186 if (repositories != null) {
187 // hardened for OSGi dynamic services
188 Iterator<Repository> it = repositories.iterator();
189 if (it.hasNext())
190 return it.next();
191 }
192 throw new IllegalStateException("No repository injected");
193 }
194
195 // /** Useful for declarative registration of OSGi services (blueprint) */
196 // public void register(Repository repository, Map<?, ?> params) {
197 // this.repository = repository;
198 // }
199 //
200 // /** Useful for declarative registration of OSGi services (blueprint) */
201 // public void unregister(Repository repository, Map<?, ?> params) {
202 // this.repository = null;
203 // }
204
205 public void setRepository(Repository repository) {
206 this.repository = repository;
207 }
208
209 public void setRepositories(List<Repository> repositories) {
210 this.repositories = repositories;
211 }
212
213 public void setDefaultUsername(String defaultUsername) {
214 this.defaultUsername = defaultUsername;
215 }
216
217 public void setDefaultPassword(String defaultPassword) {
218 this.defaultPassword = defaultPassword;
219 }
220
221 public void setForceDefaultCredentials(Boolean forceDefaultCredentials) {
222 this.forceDefaultCredentials = forceDefaultCredentials;
223 }
224
225 public void setWorkspace(String workspace) {
226 this.workspace = workspace;
227 }
228
229 protected class JcrSessionInvocationHandler implements InvocationHandler {
230
231 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable, RepositoryException {
232 Session threadSession = session.get();
233 if (threadSession == null) {
234 if ("logout".equals(method.getName()))// no need to login
235 return Void.TYPE;
236 else if ("toString".equals(method.getName()))// maybe logging
237 return "Uninitialized Argeo thread bound JCR session";
238 threadSession = login();
239 }
240
241 preCall(threadSession);
242 Object ret;
243 try {
244 ret = method.invoke(threadSession, args);
245 } catch (InvocationTargetException e) {
246 Throwable cause = e.getCause();
247 if (cause instanceof RepositoryException)
248 throw (RepositoryException) cause;
249 else
250 throw cause;
251 }
252 if ("logout".equals(method.getName())) {
253 session.remove();
254 Thread thread = Thread.currentThread();
255 removeSession(thread);
256 if (log.isTraceEnabled())
257 log.trace("Logged out JCR session (userId=" + threadSession.getUserID() + ") on thread "
258 + thread.getId());
259 }
260 return ret;
261 }
262 }
263
264 /** Monitors registered thread in order to clean up dead ones. */
265 private class MonitoringThread extends Thread {
266
267 public MonitoringThread() {
268 super("ThreadBound JCR Session Monitor");
269 }
270
271 @Override
272 public void run() {
273 while (isActive()) {
274 cleanDeadThreads();
275 }
276 }
277
278 }
279 }