2 * Copyright (C) 2007-2012 Argeo GmbH
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org
.argeo
.jcr
;
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
;
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
;
35 import org
.apache
.commons
.logging
.Log
;
36 import org
.apache
.commons
.logging
.LogFactory
;
38 /** Proxy JCR sessions and attach them to calling threads. */
40 public abstract class ThreadBoundJcrSessionFactory
{
41 private final static Log log
= LogFactory
.getLog(ThreadBoundJcrSessionFactory
.class);
43 private Repository repository
;
44 /** can be injected as list, only used if repository is null */
45 private List
<Repository
> repositories
;
47 private ThreadLocal
<Session
> session
= new ThreadLocal
<Session
>();
48 private final Session proxiedSession
;
49 /** If workspace is null, default will be used. */
50 private String workspace
= null;
52 private String defaultUsername
= "demo";
53 private String defaultPassword
= "demo";
54 private Boolean forceDefaultCredentials
= false;
56 private boolean active
= true;
59 private final List
<Thread
> threads
= Collections
.synchronizedList(new ArrayList
<Thread
>());
60 private final Map
<Long
, Session
> activeSessions
= Collections
.synchronizedMap(new HashMap
<Long
, Session
>());
61 private MonitoringThread monitoringThread
;
63 public ThreadBoundJcrSessionFactory() {
64 Class
<?
>[] interfaces
= { Session
.class };
65 proxiedSession
= (Session
) Proxy
.newProxyInstance(ThreadBoundJcrSessionFactory
.class.getClassLoader(),
66 interfaces
, new JcrSessionInvocationHandler());
69 /** Logs in to the repository using various strategies. */
70 protected synchronized Session
login() {
72 throw new ArgeoJcrException("Thread bound session factory inactive");
74 // discard session previously attached to this thread
75 Thread thread
= Thread
.currentThread();
76 if (activeSessions
.containsKey(thread
.getId())) {
77 Session oldSession
= activeSessions
.remove(thread
.getId());
82 Session newSession
= null;
83 // first try to login without credentials, assuming the underlying login
84 // module will have dealt with authentication (typically using Spring
86 if (!forceDefaultCredentials
)
88 newSession
= repository().login(workspace
);
89 } catch (LoginException e1
) {
90 log
.warn("Cannot login without credentials: " + e1
.getMessage());
91 // invalid credentials, go to the next step
92 } catch (RepositoryException e1
) {
93 // other kind of exception, fail
94 throw new ArgeoJcrException("Cannot log in to repository", e1
);
97 // log using default username / password (useful for testing purposes)
98 if (newSession
== null)
100 SimpleCredentials sc
= new SimpleCredentials(defaultUsername
, defaultPassword
.toCharArray());
101 newSession
= repository().login(sc
, workspace
);
102 } catch (RepositoryException e
) {
103 throw new ArgeoJcrException("Cannot log in to repository", e
);
106 session
.set(newSession
);
107 // Log and monitor new session
108 if (log
.isTraceEnabled())
109 log
.trace("Logged in to JCR session " + newSession
+ "; userId=" + newSession
.getUserID());
112 activeSessions
.put(thread
.getId(), newSession
);
117 public Object
getObject() {
118 return proxiedSession
;
121 public void init() throws Exception
{
122 // log.error("SHOULD NOT BE USED ANYMORE");
123 monitoringThread
= new MonitoringThread();
124 monitoringThread
.start();
127 public void dispose() throws Exception
{
128 // if (activeSessions.size() == 0)
131 if (log
.isTraceEnabled())
132 log
.trace("Cleaning up " + activeSessions
.size() + " active JCR sessions...");
135 for (Session sess
: activeSessions
.values()) {
136 JcrUtils
.logoutQuietly(sess
);
138 activeSessions
.clear();
141 protected Boolean
isActive() {
145 protected synchronized void deactivate() {
150 protected synchronized void removeSession(Thread thread
) {
153 activeSessions
.remove(thread
.getId());
154 threads
.remove(thread
);
157 protected synchronized void cleanDeadThreads() {
160 Iterator
<Thread
> it
= threads
.iterator();
161 while (it
.hasNext()) {
162 Thread thread
= it
.next();
163 if (!thread
.isAlive() && isActive()) {
164 if (activeSessions
.containsKey(thread
.getId())) {
165 Session session
= activeSessions
.get(thread
.getId());
166 activeSessions
.remove(thread
.getId());
168 if (log
.isTraceEnabled())
169 log
.trace("Cleaned up JCR session (userID=" + session
.getUserID() + ") from dead thread "
177 } catch (InterruptedException e
) {
182 public Class
<?
extends Session
> getObjectType() {
183 return Session
.class;
186 public boolean isSingleton() {
191 * Called before a method is actually called, allowing to check the session
192 * or re-login it (e.g. if authentication has changed). The default
193 * implementation returns the session.
195 protected Session
preCall(Session session
) {
199 protected Repository
repository() {
200 if (repository
!= null)
202 if (repositories
!= null) {
203 // hardened for OSGi dynamic services
204 Iterator
<Repository
> it
= repositories
.iterator();
208 throw new ArgeoJcrException("No repository injected");
211 // /** Useful for declarative registration of OSGi services (blueprint) */
212 // public void register(Repository repository, Map<?, ?> params) {
213 // this.repository = repository;
216 // /** Useful for declarative registration of OSGi services (blueprint) */
217 // public void unregister(Repository repository, Map<?, ?> params) {
218 // this.repository = null;
221 public void setRepository(Repository repository
) {
222 this.repository
= repository
;
225 public void setRepositories(List
<Repository
> repositories
) {
226 this.repositories
= repositories
;
229 public void setDefaultUsername(String defaultUsername
) {
230 this.defaultUsername
= defaultUsername
;
233 public void setDefaultPassword(String defaultPassword
) {
234 this.defaultPassword
= defaultPassword
;
237 public void setForceDefaultCredentials(Boolean forceDefaultCredentials
) {
238 this.forceDefaultCredentials
= forceDefaultCredentials
;
241 public void setWorkspace(String workspace
) {
242 this.workspace
= workspace
;
245 protected class JcrSessionInvocationHandler
implements InvocationHandler
{
247 public Object
invoke(Object proxy
, Method method
, Object
[] args
) throws Throwable
, RepositoryException
{
248 Session threadSession
= session
.get();
249 if (threadSession
== null) {
250 if ("logout".equals(method
.getName()))// no need to login
252 else if ("toString".equals(method
.getName()))// maybe logging
253 return "Uninitialized Argeo thread bound JCR session";
254 threadSession
= login();
257 preCall(threadSession
);
260 ret
= method
.invoke(threadSession
, args
);
261 } catch (InvocationTargetException e
) {
262 Throwable cause
= e
.getCause();
263 if (cause
instanceof RepositoryException
)
264 throw (RepositoryException
) cause
;
268 if ("logout".equals(method
.getName())) {
270 Thread thread
= Thread
.currentThread();
271 removeSession(thread
);
272 if (log
.isTraceEnabled())
273 log
.trace("Logged out JCR session (userId=" + threadSession
.getUserID() + ") on thread "
280 /** Monitors registered thread in order to clean up dead ones. */
281 private class MonitoringThread
extends Thread
{
283 public MonitoringThread() {
284 super("ThreadBound JCR Session Monitor");