2 * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
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.
17 package org
.argeo
.jcr
;
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
;
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
;
37 import org
.argeo
.ArgeoException
;
38 import org
.springframework
.beans
.factory
.DisposableBean
;
39 import org
.springframework
.beans
.factory
.FactoryBean
;
40 import org
.springframework
.beans
.factory
.InitializingBean
;
42 /** Proxy JCR sessions and attach them to calling threads. */
43 public class ThreadBoundJcrSessionFactory
implements FactoryBean
,
44 InitializingBean
, DisposableBean
{
45 private final static Log log
= LogFactory
46 .getLog(ThreadBoundJcrSessionFactory
.class);
48 private Repository repository
;
50 private ThreadLocal
<Session
> session
= new ThreadLocal
<Session
>();
51 private final Session proxiedSession
;
52 /** If workspace is null, default will be used. */
53 private String workspace
= null;
55 private String defaultUsername
= "demo";
56 private String defaultPassword
= "demo";
57 private Boolean forceDefaultCredentials
= false;
59 private boolean active
= true;
62 private final List
<Thread
> threads
= Collections
63 .synchronizedList(new ArrayList
<Thread
>());
64 private final Map
<Long
, Session
> activeSessions
= Collections
65 .synchronizedMap(new HashMap
<Long
, Session
>());
66 private MonitoringThread monitoringThread
;
68 public ThreadBoundJcrSessionFactory() {
69 Class
<?
>[] interfaces
= { Session
.class };
70 proxiedSession
= (Session
) Proxy
.newProxyInstance(getClass()
71 .getClassLoader(), interfaces
,
72 new JcrSessionInvocationHandler());
75 /** Logs in to the repository using various strategies. */
76 protected Session
login() {
77 // discard sesison previoussly attached to this thread
78 Thread thread
= Thread
.currentThread();
79 if (activeSessions
.containsKey(thread
.getId())) {
80 Session oldSession
= activeSessions
.remove(thread
.getId());
85 Session newSession
= null;
86 // first try to login without credentials, assuming the underlying login
87 // module will have dealt with authentication (typically using Spring
89 if (!forceDefaultCredentials
)
91 newSession
= repository
.login(workspace
);
92 } catch (LoginException e1
) {
93 log
.warn("Cannot login without credentials: " + e1
.getMessage());
94 // invalid credentials, go to the next step
95 } catch (RepositoryException e1
) {
96 // other kind of exception, fail
97 throw new ArgeoException("Cannot log in to repository", e1
);
100 // log using default username / password (useful for testing purposes)
101 if (newSession
== null)
103 SimpleCredentials sc
= new SimpleCredentials(defaultUsername
,
104 defaultPassword
.toCharArray());
105 newSession
= repository
.login(sc
, workspace
);
106 } catch (RepositoryException e
) {
107 throw new ArgeoException("Cannot log in to repository", e
);
110 session
.set(newSession
);
111 // Log and monitor new session
112 if (log
.isTraceEnabled())
113 log
.trace("Logged in to JCR session " + newSession
+ "; userId="
114 + newSession
.getUserID());
117 activeSessions
.put(thread
.getId(), newSession
);
122 public Object
getObject() {
123 return proxiedSession
;
126 public void afterPropertiesSet() throws Exception
{
127 monitoringThread
= new MonitoringThread();
128 monitoringThread
.start();
131 public synchronized void destroy() throws Exception
{
132 if (log
.isDebugEnabled())
133 log
.debug("Cleaning up " + activeSessions
.size()
134 + " active JCR sessions...");
137 for (Session sess
: activeSessions
.values()) {
140 activeSessions
.clear();
141 monitoringThread
.join(1000);
144 protected Boolean
isActive() {
148 protected synchronized void deactivate() {
153 public Class
<?
extends Session
> getObjectType() {
154 return Session
.class;
157 public boolean isSingleton() {
162 * Called before a method is actually called, allowing to check the session
163 * or re-login it (e.g. if authentication has changed). The default
164 * implementation returns the session.
166 protected Session
preCall(Session session
) {
170 public void setRepository(Repository repository
) {
171 this.repository
= repository
;
174 public void setDefaultUsername(String defaultUsername
) {
175 this.defaultUsername
= defaultUsername
;
178 public void setDefaultPassword(String defaultPassword
) {
179 this.defaultPassword
= defaultPassword
;
182 public void setForceDefaultCredentials(Boolean forceDefaultCredentials
) {
183 this.forceDefaultCredentials
= forceDefaultCredentials
;
186 public void setWorkspace(String workspace
) {
187 this.workspace
= workspace
;
190 protected class JcrSessionInvocationHandler
implements InvocationHandler
{
192 public Object
invoke(Object proxy
, Method method
, Object
[] args
)
194 Session threadSession
= session
.get();
195 if (threadSession
== null) {
196 if ("logout".equals(method
.getName()))// no need to login
198 else if ("toString".equals(method
.getName()))// maybe logging
199 return "Uninitialized Argeo thread bound JCR session";
200 threadSession
= login();
203 Object ret
= method
.invoke(threadSession
, args
);
204 if ("logout".equals(method
.getName())) {
205 synchronized (ThreadBoundJcrSessionFactory
.this) {
207 Thread thread
= Thread
.currentThread();
209 activeSessions
.remove(thread
.getId());
210 threads
.remove(thread
);
212 if (log
.isTraceEnabled())
213 log
.trace("Logged out JCR session (userId="
214 + threadSession
.getUserID() + ") on thread "
222 /** Monitors registered thread in order to clean up dead ones. */
223 private class MonitoringThread
extends Thread
{
228 Iterator
<Thread
> it
= threads
.iterator();
229 while (it
.hasNext()) {
230 Thread thread
= it
.next();
231 if (!thread
.isAlive() && isActive()) {
232 if (activeSessions
.containsKey(thread
.getId())) {
233 Session session
= activeSessions
234 .get(thread
.getId());
235 activeSessions
.remove(thread
.getId());
237 if (log
.isDebugEnabled())
238 log
.debug("Cleaned up JCR session (userID="
239 + session
.getUserID()
240 + ") from dead thread "
247 synchronized (ThreadBoundJcrSessionFactory
.this) {
249 ThreadBoundJcrSessionFactory
.this.wait(1000);
250 } catch (InterruptedException e
) {