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