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