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
;
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
;
20 import org
.apache
.commons
.logging
.Log
;
21 import org
.apache
.commons
.logging
.LogFactory
;
23 /** Proxy JCR sessions and attach them to calling threads. */
25 public abstract class ThreadBoundJcrSessionFactory
{
26 private final static Log log
= LogFactory
.getLog(ThreadBoundJcrSessionFactory
.class);
28 private Repository repository
;
29 /** can be injected as list, only used if repository is null */
30 private List
<Repository
> repositories
;
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;
37 private String defaultUsername
= "demo";
38 private String defaultPassword
= "demo";
39 private Boolean forceDefaultCredentials
= false;
41 private boolean active
= true;
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
;
48 public ThreadBoundJcrSessionFactory() {
49 Class
<?
>[] interfaces
= { Session
.class };
50 proxiedSession
= (Session
) Proxy
.newProxyInstance(ThreadBoundJcrSessionFactory
.class.getClassLoader(),
51 interfaces
, new JcrSessionInvocationHandler());
54 /** Logs in to the repository using various strategies. */
55 protected synchronized Session
login() {
57 throw new IllegalStateException("Thread bound session factory inactive");
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());
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
71 if (!forceDefaultCredentials
)
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
);
82 // log using default username / password (useful for testing purposes)
83 if (newSession
== null)
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
);
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());
97 activeSessions
.put(thread
.getId(), newSession
);
102 public Object
getObject() {
103 return proxiedSession
;
106 public void init() throws Exception
{
107 // log.error("SHOULD NOT BE USED ANYMORE");
108 monitoringThread
= new MonitoringThread();
109 monitoringThread
.start();
112 public void dispose() throws Exception
{
113 // if (activeSessions.size() == 0)
116 if (log
.isTraceEnabled())
117 log
.trace("Cleaning up " + activeSessions
.size() + " active JCR sessions...");
120 for (Session sess
: activeSessions
.values()) {
121 JcrUtils
.logoutQuietly(sess
);
123 activeSessions
.clear();
126 protected Boolean
isActive() {
130 protected synchronized void deactivate() {
135 protected synchronized void removeSession(Thread thread
) {
138 activeSessions
.remove(thread
.getId());
139 threads
.remove(thread
);
142 protected synchronized void cleanDeadThreads() {
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());
153 if (log
.isTraceEnabled())
154 log
.trace("Cleaned up JCR session (userID=" + session
.getUserID() + ") from dead thread "
162 } catch (InterruptedException e
) {
167 public Class
<?
extends Session
> getObjectType() {
168 return Session
.class;
171 public boolean isSingleton() {
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.
180 protected Session
preCall(Session session
) {
184 protected Repository
repository() {
185 if (repository
!= null)
187 if (repositories
!= null) {
188 // hardened for OSGi dynamic services
189 Iterator
<Repository
> it
= repositories
.iterator();
193 throw new IllegalStateException("No repository injected");
196 // /** Useful for declarative registration of OSGi services (blueprint) */
197 // public void register(Repository repository, Map<?, ?> params) {
198 // this.repository = repository;
201 // /** Useful for declarative registration of OSGi services (blueprint) */
202 // public void unregister(Repository repository, Map<?, ?> params) {
203 // this.repository = null;
206 public void setRepository(Repository repository
) {
207 this.repository
= repository
;
210 public void setRepositories(List
<Repository
> repositories
) {
211 this.repositories
= repositories
;
214 public void setDefaultUsername(String defaultUsername
) {
215 this.defaultUsername
= defaultUsername
;
218 public void setDefaultPassword(String defaultPassword
) {
219 this.defaultPassword
= defaultPassword
;
222 public void setForceDefaultCredentials(Boolean forceDefaultCredentials
) {
223 this.forceDefaultCredentials
= forceDefaultCredentials
;
226 public void setWorkspace(String workspace
) {
227 this.workspace
= workspace
;
230 protected class JcrSessionInvocationHandler
implements InvocationHandler
{
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
237 else if ("toString".equals(method
.getName()))// maybe logging
238 return "Uninitialized Argeo thread bound JCR session";
239 threadSession
= login();
242 preCall(threadSession
);
245 ret
= method
.invoke(threadSession
, args
);
246 } catch (InvocationTargetException e
) {
247 Throwable cause
= e
.getCause();
248 if (cause
instanceof RepositoryException
)
249 throw (RepositoryException
) cause
;
253 if ("logout".equals(method
.getName())) {
255 Thread thread
= Thread
.currentThread();
256 removeSession(thread
);
257 if (log
.isTraceEnabled())
258 log
.trace("Logged out JCR session (userId=" + threadSession
.getUserID() + ") on thread "
265 /** Monitors registered thread in order to clean up dead ones. */
266 private class MonitoringThread
extends Thread
{
268 public MonitoringThread() {
269 super("ThreadBound JCR Session Monitor");