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
.argeo
.api
.cms
.CmsLog
;
22 /** Proxy JCR sessions and attach them to calling threads. */
24 public abstract class ThreadBoundJcrSessionFactory
{
25 private final static CmsLog log
= CmsLog
.getLog(ThreadBoundJcrSessionFactory
.class);
27 private Repository repository
;
28 /** can be injected as list, only used if repository is null */
29 private List
<Repository
> repositories
;
31 private ThreadLocal
<Session
> session
= new ThreadLocal
<Session
>();
32 private final Session proxiedSession
;
33 /** If workspace is null, default will be used. */
34 private String workspace
= null;
36 private String defaultUsername
= "demo";
37 private String defaultPassword
= "demo";
38 private Boolean forceDefaultCredentials
= false;
40 private boolean active
= true;
43 private final List
<Thread
> threads
= Collections
.synchronizedList(new ArrayList
<Thread
>());
44 private final Map
<Long
, Session
> activeSessions
= Collections
.synchronizedMap(new HashMap
<Long
, Session
>());
45 private MonitoringThread monitoringThread
;
47 public ThreadBoundJcrSessionFactory() {
48 Class
<?
>[] interfaces
= { Session
.class };
49 proxiedSession
= (Session
) Proxy
.newProxyInstance(ThreadBoundJcrSessionFactory
.class.getClassLoader(),
50 interfaces
, new JcrSessionInvocationHandler());
53 /** Logs in to the repository using various strategies. */
54 protected synchronized Session
login() {
56 throw new IllegalStateException("Thread bound session factory inactive");
58 // discard session previously attached to this thread
59 Thread thread
= Thread
.currentThread();
60 if (activeSessions
.containsKey(thread
.getId())) {
61 Session oldSession
= activeSessions
.remove(thread
.getId());
66 Session newSession
= null;
67 // first try to login without credentials, assuming the underlying login
68 // module will have dealt with authentication (typically using Spring
70 if (!forceDefaultCredentials
)
72 newSession
= repository().login(workspace
);
73 } catch (LoginException e1
) {
74 log
.warn("Cannot login without credentials: " + e1
.getMessage());
75 // invalid credentials, go to the next step
76 } catch (RepositoryException e1
) {
77 // other kind of exception, fail
78 throw new JcrException("Cannot log in to repository", e1
);
81 // log using default username / password (useful for testing purposes)
82 if (newSession
== null)
84 SimpleCredentials sc
= new SimpleCredentials(defaultUsername
, defaultPassword
.toCharArray());
85 newSession
= repository().login(sc
, workspace
);
86 } catch (RepositoryException e
) {
87 throw new JcrException("Cannot log in to repository", e
);
90 session
.set(newSession
);
91 // Log and monitor new session
92 if (log
.isTraceEnabled())
93 log
.trace("Logged in to JCR session " + newSession
+ "; userId=" + newSession
.getUserID());
96 activeSessions
.put(thread
.getId(), newSession
);
101 public Object
getObject() {
102 return proxiedSession
;
105 public void init() throws Exception
{
106 // log.error("SHOULD NOT BE USED ANYMORE");
107 monitoringThread
= new MonitoringThread();
108 monitoringThread
.start();
111 public void dispose() throws Exception
{
112 // if (activeSessions.size() == 0)
115 if (log
.isTraceEnabled())
116 log
.trace("Cleaning up " + activeSessions
.size() + " active JCR sessions...");
119 for (Session sess
: activeSessions
.values()) {
120 JcrUtils
.logoutQuietly(sess
);
122 activeSessions
.clear();
125 protected Boolean
isActive() {
129 protected synchronized void deactivate() {
134 protected synchronized void removeSession(Thread thread
) {
137 activeSessions
.remove(thread
.getId());
138 threads
.remove(thread
);
141 protected synchronized void cleanDeadThreads() {
144 Iterator
<Thread
> it
= threads
.iterator();
145 while (it
.hasNext()) {
146 Thread thread
= it
.next();
147 if (!thread
.isAlive() && isActive()) {
148 if (activeSessions
.containsKey(thread
.getId())) {
149 Session session
= activeSessions
.get(thread
.getId());
150 activeSessions
.remove(thread
.getId());
152 if (log
.isTraceEnabled())
153 log
.trace("Cleaned up JCR session (userID=" + session
.getUserID() + ") from dead thread "
161 } catch (InterruptedException e
) {
166 public Class
<?
extends Session
> getObjectType() {
167 return Session
.class;
170 public boolean isSingleton() {
175 * Called before a method is actually called, allowing to check the session or
176 * re-login it (e.g. if authentication has changed). The default implementation
177 * returns the session.
179 protected Session
preCall(Session session
) {
183 protected Repository
repository() {
184 if (repository
!= null)
186 if (repositories
!= null) {
187 // hardened for OSGi dynamic services
188 Iterator
<Repository
> it
= repositories
.iterator();
192 throw new IllegalStateException("No repository injected");
195 // /** Useful for declarative registration of OSGi services (blueprint) */
196 // public void register(Repository repository, Map<?, ?> params) {
197 // this.repository = repository;
200 // /** Useful for declarative registration of OSGi services (blueprint) */
201 // public void unregister(Repository repository, Map<?, ?> params) {
202 // this.repository = null;
205 public void setRepository(Repository repository
) {
206 this.repository
= repository
;
209 public void setRepositories(List
<Repository
> repositories
) {
210 this.repositories
= repositories
;
213 public void setDefaultUsername(String defaultUsername
) {
214 this.defaultUsername
= defaultUsername
;
217 public void setDefaultPassword(String defaultPassword
) {
218 this.defaultPassword
= defaultPassword
;
221 public void setForceDefaultCredentials(Boolean forceDefaultCredentials
) {
222 this.forceDefaultCredentials
= forceDefaultCredentials
;
225 public void setWorkspace(String workspace
) {
226 this.workspace
= workspace
;
229 protected class JcrSessionInvocationHandler
implements InvocationHandler
{
231 public Object
invoke(Object proxy
, Method method
, Object
[] args
) throws Throwable
, RepositoryException
{
232 Session threadSession
= session
.get();
233 if (threadSession
== null) {
234 if ("logout".equals(method
.getName()))// no need to login
236 else if ("toString".equals(method
.getName()))// maybe logging
237 return "Uninitialized Argeo thread bound JCR session";
238 threadSession
= login();
241 preCall(threadSession
);
244 ret
= method
.invoke(threadSession
, args
);
245 } catch (InvocationTargetException e
) {
246 Throwable cause
= e
.getCause();
247 if (cause
instanceof RepositoryException
)
248 throw (RepositoryException
) cause
;
252 if ("logout".equals(method
.getName())) {
254 Thread thread
= Thread
.currentThread();
255 removeSession(thread
);
256 if (log
.isTraceEnabled())
257 log
.trace("Logged out JCR session (userId=" + threadSession
.getUserID() + ") on thread "
264 /** Monitors registered thread in order to clean up dead ones. */
265 private class MonitoringThread
extends Thread
{
267 public MonitoringThread() {
268 super("ThreadBound JCR Session Monitor");