View Javadoc
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  
38  /** Proxy JCR sessions and attach them to calling threads. */
39  @Deprecated
40  public abstract class ThreadBoundJcrSessionFactory {
41  	private final static Log log = LogFactory.getLog(ThreadBoundJcrSessionFactory.class);
42  
43  	private Repository repository;
44  	/** can be injected as list, only used if repository is null */
45  	private List<Repository> repositories;
46  
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;
51  
52  	private String defaultUsername = "demo";
53  	private String defaultPassword = "demo";
54  	private Boolean forceDefaultCredentials = false;
55  
56  	private boolean active = true;
57  
58  	// monitoring
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;
62  
63  	public ThreadBoundJcrSessionFactory() {
64  		Class<?>[] interfaces = { Session.class };
65  		proxiedSession = (Session) Proxy.newProxyInstance(ThreadBoundJcrSessionFactory.class.getClassLoader(),
66  				interfaces, new JcrSessionInvocationHandler());
67  	}
68  
69  	/** Logs in to the repository using various strategies. */
70  	protected synchronized Session login() {
71  		if (!isActive())
72  			throw new ArgeoJcrException("Thread bound session factory inactive");
73  
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());
78  			oldSession.logout();
79  			session.remove();
80  		}
81  
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
85  		// Security)
86  		if (!forceDefaultCredentials)
87  			try {
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);
95  			}
96  
97  		// log using default username / password (useful for testing purposes)
98  		if (newSession == null)
99  			try {
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);
104 			}
105 
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());
110 
111 		// monitoring
112 		activeSessions.put(thread.getId(), newSession);
113 		threads.add(thread);
114 		return newSession;
115 	}
116 
117 	public Object getObject() {
118 		return proxiedSession;
119 	}
120 
121 	public void init() throws Exception {
122 		// log.error("SHOULD NOT BE USED ANYMORE");
123 		monitoringThread = new MonitoringThread();
124 		monitoringThread.start();
125 	}
126 
127 	public void dispose() throws Exception {
128 		// if (activeSessions.size() == 0)
129 		// return;
130 
131 		if (log.isTraceEnabled())
132 			log.trace("Cleaning up " + activeSessions.size() + " active JCR sessions...");
133 
134 		deactivate();
135 		for (Session sess : activeSessions.values()) {
136 			JcrUtils.logoutQuietly(sess);
137 		}
138 		activeSessions.clear();
139 	}
140 
141 	protected Boolean isActive() {
142 		return active;
143 	}
144 
145 	protected synchronized void deactivate() {
146 		active = false;
147 		notifyAll();
148 	}
149 
150 	protected synchronized void removeSession(Thread thread) {
151 		if (!isActive())
152 			return;
153 		activeSessions.remove(thread.getId());
154 		threads.remove(thread);
155 	}
156 
157 	protected synchronized void cleanDeadThreads() {
158 		if (!isActive())
159 			return;
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());
167 					session.logout();
168 					if (log.isTraceEnabled())
169 						log.trace("Cleaned up JCR session (userID=" + session.getUserID() + ") from dead thread "
170 								+ thread.getId());
171 				}
172 				it.remove();
173 			}
174 		}
175 		try {
176 			wait(1000);
177 		} catch (InterruptedException e) {
178 			// silent
179 		}
180 	}
181 
182 	public Class<? extends Session> getObjectType() {
183 		return Session.class;
184 	}
185 
186 	public boolean isSingleton() {
187 		return true;
188 	}
189 
190 	/**
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.
194 	 */
195 	protected Session preCall(Session session) {
196 		return session;
197 	}
198 
199 	protected Repository repository() {
200 		if (repository != null)
201 			return repository;
202 		if (repositories != null) {
203 			// hardened for OSGi dynamic services
204 			Iterator<Repository> it = repositories.iterator();
205 			if (it.hasNext())
206 				return it.next();
207 		}
208 		throw new ArgeoJcrException("No repository injected");
209 	}
210 
211 	// /** Useful for declarative registration of OSGi services (blueprint) */
212 	// public void register(Repository repository, Map<?, ?> params) {
213 	// this.repository = repository;
214 	// }
215 	//
216 	// /** Useful for declarative registration of OSGi services (blueprint) */
217 	// public void unregister(Repository repository, Map<?, ?> params) {
218 	// this.repository = null;
219 	// }
220 
221 	public void setRepository(Repository repository) {
222 		this.repository = repository;
223 	}
224 
225 	public void setRepositories(List<Repository> repositories) {
226 		this.repositories = repositories;
227 	}
228 
229 	public void setDefaultUsername(String defaultUsername) {
230 		this.defaultUsername = defaultUsername;
231 	}
232 
233 	public void setDefaultPassword(String defaultPassword) {
234 		this.defaultPassword = defaultPassword;
235 	}
236 
237 	public void setForceDefaultCredentials(Boolean forceDefaultCredentials) {
238 		this.forceDefaultCredentials = forceDefaultCredentials;
239 	}
240 
241 	public void setWorkspace(String workspace) {
242 		this.workspace = workspace;
243 	}
244 
245 	protected class JcrSessionInvocationHandler implements InvocationHandler {
246 
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
251 					return Void.TYPE;
252 				else if ("toString".equals(method.getName()))// maybe logging
253 					return "Uninitialized Argeo thread bound JCR session";
254 				threadSession = login();
255 			}
256 
257 			preCall(threadSession);
258 			Object ret;
259 			try {
260 				ret = method.invoke(threadSession, args);
261 			} catch (InvocationTargetException e) {
262 				Throwable cause = e.getCause();
263 				if (cause instanceof RepositoryException)
264 					throw (RepositoryException) cause;
265 				else
266 					throw cause;
267 			}
268 			if ("logout".equals(method.getName())) {
269 				session.remove();
270 				Thread thread = Thread.currentThread();
271 				removeSession(thread);
272 				if (log.isTraceEnabled())
273 					log.trace("Logged out JCR session (userId=" + threadSession.getUserID() + ") on thread "
274 							+ thread.getId());
275 			}
276 			return ret;
277 		}
278 	}
279 
280 	/** Monitors registered thread in order to clean up dead ones. */
281 	private class MonitoringThread extends Thread {
282 
283 		public MonitoringThread() {
284 			super("ThreadBound JCR Session Monitor");
285 		}
286 
287 		@Override
288 		public void run() {
289 			while (isActive()) {
290 				cleanDeadThreads();
291 			}
292 		}
293 
294 	}
295 }