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