javax.jcr;version="[2.0.0,3.0.0)",
org.argeo.jcr,
org.argeo.security,
+ org.argeo.security.jcr,
org.argeo.security.ldap,
org.argeo.security.ldap.jcr,
org.argeo.security.ldap.nature,
</entry>
</map>
</property>
+ </bean>
+ <bean id="nodeSession" class="org.argeo.security.jcr.SecureThreadBoundSession">
+ <property name="repository" ref="nodeRepository" />
</bean>
+
</beans>
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">\r
\r
<!-- REFERENCES -->\r
-<!-- <list id="userNatureMappers" interface="org.argeo.security.ldap.UserNatureMapper" -->\r
-<!-- cardinality="0..N" /> -->\r
-<!-- <reference id="repositoryFactory" interface="javax.jcr.RepositoryFactory" -->\r
-<!-- cardinality="0..1"> -->\r
-<!-- <listener ref="jcrUserDetailsContextMapper" bind-method="register" -->\r
-<!-- unbind-method="unregister" /> -->\r
-<!-- </reference> -->\r
- <reference id="nodeSession" interface="javax.jcr.Session"\r
+ <!-- <list id="userNatureMappers" interface="org.argeo.security.ldap.UserNatureMapper" -->\r
+ <!-- cardinality="0..N" /> -->\r
+ <!-- <reference id="repositoryFactory" interface="javax.jcr.RepositoryFactory" -->\r
+ <!-- cardinality="0..1"> -->\r
+ <!-- <listener ref="jcrUserDetailsContextMapper" bind-method="register" -->\r
+ <!-- unbind-method="unregister" /> -->\r
+ <!-- </reference> -->\r
+ <reference id="nodeRepository" interface="javax.jcr.Repository"\r
filter="(argeo.jcr.repository.alias=node)" />\r
<reference id="systemExecutionService" interface="org.argeo.security.SystemExecutionService" />\r
\r
<service ref="ldapAuthenticationProvider"\r
interface="org.springframework.security.providers.AuthenticationProvider"\r
context-class-loader="service-provider" />\r
- \r
+\r
<service ref="securityDao" interface="org.argeo.security.CurrentUserDao"\r
context-class-loader="service-provider" />\r
<service ref="securityDao" interface="org.argeo.security.UserAdminDao"\r
<property name="rolePrefix" value="${argeo.security.rolePrefix}" />
</bean>
- <bean id="userDetailsManager"
- class="org.springframework.security.userdetails.ldap.LdapUserDetailsManager">
+ <bean id="userDetailsManager" class="org.argeo.security.ldap.ArgeoLdapUserDetailsManager">
<constructor-arg ref="contextSource" />
<property name="groupSearchBase" value="${argeo.ldap.groupBase}" />
<property name="groupMemberAttributeName" value="${argeo.ldap.groupMemberAttribute}" />
<property name="usernameMapper" ref="usernameMapper" />
<property name="userDetailsMapper" ref="jcrUserDetailsContextMapper" />
+ <property name="passwordEncoder" ref="passwordEncoder" />
+ <property name="passwordAttributeName" value="${argeo.ldap.passwordAttribute}" />
</bean>
-
- <!-- <bean id="userDetailsService" -->
- <!-- class="org.springframework.security.userdetails.ldap.LdapUserDetailsManager"> -->
- <!-- <constructor-arg> -->
- <!-- <bean -->
- <!-- class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch"> -->
- <!-- <constructor-arg value="${argeo.ldap.userBase}" /> -->
- <!-- <constructor-arg value="(${argeo.ldap.usernameAttribute}={0})" /> -->
- <!-- <constructor-arg ref="contextSource" /> -->
- <!-- </bean> -->
- <!-- </constructor-arg> -->
- <!-- <constructor-arg ref="authoritiesPopulator" /> -->
- <!-- <property name="userDetailsMapper" ref="jcrUserDetailsContextMapper"
- /> -->
- <!-- </bean> -->
</beans>
<list id="authenticationProviders"\r
interface="org.springframework.security.providers.AuthenticationProvider"\r
cardinality="0..N">\r
- <listener ref="authenticationManager" bind-method="register"\r
+ <listener ref="authenticationProvidersRegister" bind-method="register"\r
unbind-method="unregister" />\r
</list>\r
\r
<property name="systemAuthenticationKey" value="${argeo.security.systemKey}" />
</bean>
- <bean id="authenticationManager" class="org.argeo.security.core.ArgeoAuthenticationManager">
+ <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager">
<property name="providers">
+ <bean factory-bean="authenticationProvidersRegister"
+ factory-method="getProviders" />
+ </property>
+ </bean>
+ <bean id="authenticationProvidersRegister" class="org.argeo.security.core.AuthenticationProvidersRegister">
+ <property name="defaultProviders">
<list>
<bean class="org.springframework.security.adapters.AuthByAdapterProvider">
<property name="key" value="${argeo.security.systemKey}" />
package org.argeo.security.equinox;
import java.util.Map;
-import java.util.concurrent.Executor;
+import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.TextOutputCallback;
import javax.security.auth.login.LoginException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.argeo.security.SiteAuthenticationToken;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationManager;
/** Login module which caches one subject per thread. */
public class SpringLoginModule extends SecurityContextLoginModule {
+ private final static Log log = LogFactory.getLog(SpringLoginModule.class);
+
private AuthenticationManager authenticationManager;
- private Executor systemExecutor;
private CallbackHandler callbackHandler;
+ private Subject subject;
+
public SpringLoginModule() {
}
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map sharedState, Map options) {
super.initialize(subject, callbackHandler, sharedState, options);
- // this.subject.set(subject);
this.callbackHandler = callbackHandler;
+ this.subject = subject;
}
public boolean login() throws LoginException {
+ // try to retrieve Authentication from Subject
+ Set<Authentication> auths = subject.getPrincipals(Authentication.class);
+ if (auths.size() > 0)
+ SecurityContextHolder.getContext().setAuthentication(
+ auths.iterator().next());
+
// thread already logged in
if (SecurityContextHolder.getContext().getAuthentication() != null)
return super.login();
- // if (getSubject().getPrincipals(Authentication.class).size() == 1) {
- // registerAuthentication(getSubject()
- // .getPrincipals(Authentication.class).iterator().next());
- // return super.login();
- // } else if (getSubject().getPrincipals(Authentication.class).size() >
- // 1) {
- // throw new LoginException(
- // "Multiple Authentication principals not supported: "
- // + getSubject().getPrincipals(Authentication.class));
- // } else {
// ask for username and password
Callback label = new TextOutputCallback(TextOutputCallback.INFORMATION,
"Required login");
username, password, url, workspace);
try {
-
Authentication authentication = authenticationManager
.authenticate(credentials);
registerAuthentication(authentication);
boolean res = super.login();
- // if (log.isDebugEnabled())
- // log.debug("User " + username + " logged in");
return res;
} catch (BadCredentialsException bce) {
throw bce;
@Override
public boolean logout() throws LoginException {
+// if (log.isDebugEnabled())
+// log.debug("logout subject=" + subject);
return super.logout();
}
AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
-
- public void setSystemExecutor(Executor systemExecutor) {
- this.systemExecutor = systemExecutor;
- }
-
- // protected Subject getSubject() {
- // return subject.get();
- // }
-
}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<beans xmlns="http://www.springframework.org/schema/beans"\r
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">\r
+\r
+ <bean id="nodeSession" class="org.argeo.security.jcr.SecureThreadBoundSession">\r
+ <property name="repository" ref="nodeRepository" />\r
+ </bean>\r
+</beans>
\ No newline at end of file
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"\r
osgi:default-timeout="30000">\r
\r
- <reference id="jcrSession" interface="javax.jcr.Session"\r
+ <reference id="nodeRepository" interface="javax.jcr.Repository"\r
filter="(argeo.jcr.repository.alias=node)" />\r
<reference id="userAdminService" interface="org.argeo.security.UserAdminService" />\r
<reference id="currentUserService" interface="org.argeo.security.CurrentUserService" />\r
<bean id="adminUsersView" class="org.argeo.security.ui.admin.views.UsersView"
scope="prototype">
<!-- <property name="userAdminService" ref="userAdminService" /> -->
- <property name="session" ref="jcrSession" />
+ <property name="session" ref="nodeSession" />
</bean>
<bean id="adminRolesView" class="org.argeo.security.ui.admin.views.RolesView"
scope="prototype">
package org.argeo.security.ui.rap;
import java.security.PrivilegedAction;
-import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.ErrorDialog;
-import org.eclipse.rwt.RWT;
import org.eclipse.rwt.lifecycle.IEntryPoint;
import org.eclipse.rwt.service.SessionStoreEvent;
import org.eclipse.rwt.service.SessionStoreListener;
import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
import org.eclipse.ui.application.WorkbenchAdvisor;
import org.eclipse.ui.application.WorkbenchWindowAdvisor;
-import org.springframework.security.Authentication;
-import org.springframework.security.context.SecurityContextHolder;
public class SecureEntryPoint implements IEntryPoint, SessionStoreListener {
private Log log = LogFactory.getLog(SecureEntryPoint.class);
- private final static String SECURITY_CONTEXT_ATTRIBUTE = "securityContextAttribute";
-
@Override
public int createUI() {
-// log.debug("THREAD=" + Thread.currentThread().getId()
-// + ", RWT.getSessionStore().getId()="
-// + RWT.getSessionStore().getId());
-
- Authentication authen = (Authentication) RWT.getSessionStore()
- .getAttribute(SECURITY_CONTEXT_ATTRIBUTE);
- if (authen != null)
- SecurityContextHolder.getContext().setAuthentication(authen);
+ // log.debug("THREAD=" + Thread.currentThread().getId()
+ // + ", RWT.getSessionStore().getId()="
+ // + RWT.getSessionStore().getId());
Integer returnCode = null;
Display display = PlatformUI.createDisplay();
Boolean retry = true;
while (retry) {
try {
- // if (authen == null)
- // SecureRapActivator.getLoginContext().login();
+ // force login in order to give Spring Security a chance to
+ // load
+ SecureRapActivator.getLoginContext().login();
subject = SecureRapActivator.getLoginContext().getSubject();
- Set<Authentication> auths = subject
- .getPrincipals(Authentication.class);
- if (auths.size() > 0)
- SecurityContextHolder.getContext().setAuthentication(
- auths.iterator().next());
- // authen = SecurityContextHolder.getContext()
- // .getAuthentication();
- // RWT.getSessionStore().setAttribute(
- // SECURITY_CONTEXT_ATTRIBUTE, authen);
retry = false;
} catch (LoginException e) {
Error.show("Cannot login", e);
<bean id="openChangePasswordDialog" class="org.argeo.security.ui.commands.OpenChangePasswordDialog"
scope="prototype">
- <property name="currentUserService" ref="currentUserService" />
+ <property name="userDetailsManager" ref="userDetailsManager" />
</bean>
</beans>
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"\r
osgi:default-timeout="30000">\r
\r
- <reference id="currentUserService" interface="org.argeo.security.CurrentUserService" />\r
+ <reference id="userDetailsManager"\r
+ interface="org.springframework.security.userdetails.UserDetailsManager" />\r
</beans:beans>
\ No newline at end of file
package org.argeo.security.ui.commands;
-import org.argeo.security.CurrentUserService;
import org.argeo.security.ui.dialogs.ChangePasswordDialog;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.ui.handlers.HandlerUtil;
+import org.springframework.security.userdetails.UserDetailsManager;
/** Opens the change password dialog. */
public class OpenChangePasswordDialog extends AbstractHandler {
- private CurrentUserService currentUserService;
+ private UserDetailsManager userDetailsManager;
public Object execute(ExecutionEvent event) throws ExecutionException {
ChangePasswordDialog dialog = new ChangePasswordDialog(
- HandlerUtil.getActiveShell(event), currentUserService);
+ HandlerUtil.getActiveShell(event), userDetailsManager);
dialog.open();
return null;
}
- public void setCurrentUserService(CurrentUserService currentUserService) {
- this.currentUserService = currentUserService;
+ public void setUserDetailsManager(UserDetailsManager userDetailsManager) {
+ this.userDetailsManager = userDetailsManager;
}
}
package org.argeo.security.ui.dialogs;
import org.argeo.ArgeoException;
-import org.argeo.security.CurrentUserService;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
+import org.springframework.security.userdetails.UserDetailsManager;
/** Dialog to change the current user password */
public class ChangePasswordDialog extends TitleAreaDialog {
private Text currentPassword, newPassword1, newPassword2;
- private CurrentUserService securityService;
+ private UserDetailsManager userDetailsManager;
public ChangePasswordDialog(Shell parentShell,
- CurrentUserService securityService) {
+ UserDetailsManager securityService) {
super(parentShell);
- this.securityService = securityService;
+ this.userDetailsManager = securityService;
}
protected Point getInitialSize() {
protected void okPressed() {
if (!newPassword1.getText().equals(newPassword2.getText()))
throw new ArgeoException("Passwords are different");
- securityService.updateCurrentUserPassword(currentPassword.getText(),
+ userDetailsManager.changePassword(currentPassword.getText(),
newPassword1.getText());
close();
}
<Export-Package>
org.argeo.security.*
</Export-Package>
+ <!-- We need to exclude some packages which are added by BND but cause
+ use package conflict with some deployments -->
<Import-Package>
org.springframework.context,
org.springframework.beans.factory,
/**
* Access to user backend for the currently logged in user
*/
+@Deprecated
public interface CurrentUserDao {
public void updateCurrentUserPassword(String oldPassword, String newPassword);
import java.util.Map;
+@Deprecated
public interface CurrentUserService {
public ArgeoUser getCurrentUser();
+++ /dev/null
-package org.argeo.security.core;
-
-import java.util.Map;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.security.providers.AuthenticationProvider;
-import org.springframework.security.providers.ProviderManager;
-
-public class ArgeoAuthenticationManager extends ProviderManager {
- private Log log = LogFactory.getLog(ArgeoAuthenticationManager.class);
-
- @SuppressWarnings("unchecked")
- public void register(AuthenticationProvider authenticationProvider,
- Map<String, String> parameters) {
- getProviders().add(authenticationProvider);
- if (log.isDebugEnabled())
- log.debug("Registered authentication provider " + parameters);
- }
-
- public void unregister(AuthenticationProvider authenticationProvider,
- Map<String, String> parameters) {
- getProviders().remove(authenticationProvider);
- if (log.isDebugEnabled())
- log.debug("Unregistered authentication provider " + parameters);
- }
-
-}
--- /dev/null
+package org.argeo.security.core;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.InitializingBean;
+
+/**
+ * Maintains a list of authentication providers injected in to a provider
+ * manager, in order to avoid issues with OSGi services and use packages.
+ */
+public class AuthenticationProvidersRegister implements InitializingBean {
+ private Log log = LogFactory.getLog(AuthenticationProvidersRegister.class);
+
+ private List<Object> providers = new ArrayList<Object>();
+ private List<Object> defaultProviders = new ArrayList<Object>();
+
+ public void register(Object authenticationProvider,
+ Map<String, String> parameters) {
+ providers.add(authenticationProvider);
+ if (log.isDebugEnabled())
+ log.debug("Registered authentication provider " + parameters);
+ }
+
+ public void unregister(Object authenticationProvider,
+ Map<String, String> parameters) {
+ providers.remove(authenticationProvider);
+ if (log.isDebugEnabled())
+ log.debug("Unregistered authentication provider " + parameters);
+ }
+
+ public List<Object> getProviders() {
+ return providers;
+ }
+
+ public void setDefaultProviders(
+ List<Object> defaultProviders) {
+ this.defaultProviders = defaultProviders;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ providers.addAll(defaultProviders);
+ }
+
+}
import org.argeo.security.CurrentUserService;
import org.argeo.security.UserNature;
+@Deprecated
public class DefaultCurrentUserService implements CurrentUserService {
private CurrentUserDao currentUserDao;
--- /dev/null
+package org.argeo.security.jcr;
+
+import javax.jcr.Session;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.jcr.ThreadBoundJcrSessionFactory;
+import org.springframework.security.Authentication;
+import org.springframework.security.context.SecurityContextHolder;
+
+public class SecureThreadBoundSession extends ThreadBoundJcrSessionFactory {
+ private final static Log log = LogFactory
+ .getLog(SecureThreadBoundSession.class);
+
+ @Override
+ protected Session preCall(Session session) {
+ Authentication authentication = SecurityContextHolder.getContext()
+ .getAuthentication();
+ if (authentication != null) {
+ if (!session.getUserID().equals(
+ authentication.getPrincipal().toString())) {
+ log.warn("Current session has user ID " + session.getUserID()
+ + " while authentication is " + authentication
+ + ". Re-login.");
+ return login();
+ }
+ }
+ return super.preCall(session);
+ }
+
+}
principals.add(new AnonymousPrincipal());
else
for (GrantedAuthority ga : authen.getAuthorities()) {
+ // FIXME: make it more generic
if (adminRole.equals(ga.getAuthority()))
principals.add(new AdminPrincipal(authen.getName()));
}
return principals;
}
+ /**
+ * Super implementation removes all {@link Principal}, the Spring
+ * {@link org.springframework.security.Authentication} as well. Here we
+ * simply clear Jackrabbit related {@link Principal}s.
+ */
+ @Override
+ public boolean logout() throws LoginException {
+ Set<AdminPrincipal> adminPrincipals = subject
+ .getPrincipals(AdminPrincipal.class);
+ Set<AnonymousPrincipal> anonymousPrincipals = subject
+ .getPrincipals(AnonymousPrincipal.class);
+ Set<SimpleCredentials> thisCredentials = subject
+ .getPublicCredentials(SimpleCredentials.class);
+ if (thisCredentials != null)
+ thisCredentials.clear();
+ if (adminPrincipals != null)
+ adminPrincipals.clear();
+ if (anonymousPrincipals != null)
+ anonymousPrincipals.clear();
+ return true;
+ }
+
@SuppressWarnings("rawtypes")
@Override
protected void doInit(CallbackHandler callbackHandler, Session session,
--- /dev/null
+package org.argeo.security.ldap;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Random;
+
+import org.springframework.ldap.core.ContextSource;
+import org.springframework.security.providers.encoding.PasswordEncoder;
+import org.springframework.security.userdetails.ldap.LdapUserDetailsManager;
+
+/** Extends {@link LdapUserDetailsManager} by adding password encoding support. */
+public class ArgeoLdapUserDetailsManager extends LdapUserDetailsManager {
+ private PasswordEncoder passwordEncoder;
+ private final Random random;
+
+ public ArgeoLdapUserDetailsManager(ContextSource contextSource) {
+ super(contextSource);
+ this.random = createRandom();
+ }
+
+ private static Random createRandom() {
+ try {
+ return SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ return new Random(System.currentTimeMillis());
+ }
+ }
+
+ @Override
+ public void changePassword(String oldPassword, String newPassword) {
+ super.changePassword(oldPassword, encodePassword(newPassword));
+ }
+
+ protected String encodePassword(String password) {
+ if (!password.startsWith("{")) {
+ byte[] salt = new byte[16];
+ random.nextBytes(salt);
+ return passwordEncoder.encodePassword(password, salt);
+ } else {
+ return password;
+ }
+ }
+
+ public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
+ this.passwordEncoder = passwordEncoder;
+ }
+
+}
import java.util.concurrent.Executor;
import javax.jcr.Node;
-import javax.jcr.Repository;
import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
import javax.jcr.Session;
import javax.jcr.nodetype.NodeType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoJcrConstants;
import org.argeo.jcr.ArgeoNames;
import org.argeo.jcr.ArgeoTypes;
import org.argeo.jcr.JcrUtils;
userHomePathT.append(userHomepath);
}
};
- if (SecurityContextHolder.getContext().getAuthentication() == null)// authentication
+
+ if (SecurityContextHolder.getContext().getAuthentication() == null) {
+ // authentication
systemExecutor.execute(action);
- else
+ JcrUtils.logoutQuietly(session);
+ } else {
+ // authenticated user
action.run();
+ }
// password
byte[] arr = (byte[]) ctx
final JcrUserDetails jcrUserDetails = (JcrUserDetails) user;
// systemExecutor.execute(new Runnable() {
// public void run() {
-// Session session = null;
+ // Session session = null;
try {
// Repository nodeRepo = JcrUtils.getRepositoryByAlias(
// repositoryFactory, ArgeoJcrConstants.ALIAS_NODE);
</beans:entry>\r
</service-properties>\r
</service>\r
-\r
- <service ref="nodeJcrSession" interface="javax.jcr.Session">\r
- <service-properties>\r
- <beans:entry value="node">\r
- <beans:key>\r
- <util:constant\r
- static-field="org.argeo.jcr.ArgeoJcrConstants.JCR_REPOSITORY_ALIAS" />\r
- </beans:key>\r
- </beans:entry>\r
- </service-properties>\r
- </service>\r
</beans:beans>
\ No newline at end of file
</property>
<property name="systemExecutor" ref="systemExecutionService" />
</bean>
-
- <bean id="nodeJcrSession" class="org.argeo.jcr.ThreadBoundJcrSessionFactory">
- <property name="repository" ref="nodeJcrRepository" />
- <property name="workspace" value="${argeo.node.repo.workspace}" />
- </bean>
</beans>
\ No newline at end of file
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import javax.jcr.LoginException;
import javax.jcr.Repository;
import org.argeo.ArgeoException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
/** Proxy JCR sessions and attach them to calling threads. */
public class ThreadBoundJcrSessionFactory implements FactoryBean,
- DisposableBean {
+ InitializingBean, DisposableBean {
private final static Log log = LogFactory
.getLog(ThreadBoundJcrSessionFactory.class);
private Repository repository;
- private final List<Session> activeSessions = Collections
- .synchronizedList(new ArrayList<Session>());
private ThreadLocal<Session> session = new ThreadLocal<Session>();
- private boolean destroying = false;
private final Session proxiedSession;
/** If workspace is null, default will be used. */
private String workspace = null;
private String defaultPassword = "demo";
private Boolean forceDefaultCredentials = false;
+ private boolean active = true;
+
+ // monitoring
+ private final List<Thread> threads = Collections
+ .synchronizedList(new ArrayList<Thread>());
+ private final Map<Long, Session> activeSessions = Collections
+ .synchronizedMap(new HashMap<Long, Session>());
+ private MonitoringThread monitoringThread;
+
public ThreadBoundJcrSessionFactory() {
Class<?>[] interfaces = { Session.class };
proxiedSession = (Session) Proxy.newProxyInstance(getClass()
/** Logs in to the repository using various strategies. */
protected Session login() {
+ // discard sesison previoussly attached to this thread
+ Thread thread = Thread.currentThread();
+ if (activeSessions.containsKey(thread.getId())) {
+ Session oldSession = activeSessions.remove(thread.getId());
+ oldSession.logout();
+ session.remove();
+ }
+
Session newSession = null;
// first try to login without credentials, assuming the underlying login
// module will have dealt with authentication (typically using Spring
throw new ArgeoException("Cannot log in to repository", e);
}
+ session.set(newSession);
// Log and monitor new session
if (log.isTraceEnabled())
log.trace("Logged in to JCR session " + newSession + "; userId="
+ newSession.getUserID());
- activeSessions.add(newSession);
+
+ // monitoring
+ activeSessions.put(thread.getId(), newSession);
+ threads.add(thread);
return newSession;
}
return proxiedSession;
}
- public void destroy() throws Exception {
+ public void afterPropertiesSet() throws Exception {
+ monitoringThread = new MonitoringThread();
+ monitoringThread.start();
+ }
+
+ public synchronized void destroy() throws Exception {
if (log.isDebugEnabled())
log.debug("Cleaning up " + activeSessions.size()
+ " active JCR sessions...");
- destroying = true;
- for (Session sess : activeSessions) {
+ deactivate();
+ for (Session sess : activeSessions.values()) {
sess.logout();
}
activeSessions.clear();
+ monitoringThread.join(1000);
+ }
+
+ protected Boolean isActive() {
+ return active;
+ }
+
+ protected synchronized void deactivate() {
+ active = false;
+ notifyAll();
}
public Class<? extends Session> getObjectType() {
return true;
}
+ /**
+ * Called before a method is actually called, allowing to check the session
+ * or re-login it (e.g. if authentication has changed). The default
+ * implementation returns the session.
+ */
+ protected Session preCall(Session session) {
+ return session;
+ }
+
public void setRepository(Repository repository) {
this.repository = repository;
}
else if ("toString".equals(method.getName()))// maybe logging
return "Uninitialized Argeo thread bound JCR session";
threadSession = login();
- session.set(threadSession);
}
Object ret = method.invoke(threadSession, args);
if ("logout".equals(method.getName())) {
- session.remove();
- if (!destroying)
- activeSessions.remove(threadSession);
- if (log.isTraceEnabled())
- log.trace("Logged out from JCR session " + threadSession
- + "; userId=" + threadSession.getUserID());
+ synchronized (ThreadBoundJcrSessionFactory.this) {
+ session.remove();
+ Thread thread = Thread.currentThread();
+ if (isActive()) {
+ activeSessions.remove(thread.getId());
+ threads.remove(thread);
+ }
+ if (log.isTraceEnabled())
+ log.trace("Logged out JCR session (userId="
+ + threadSession.getUserID() + ") on thread "
+ + thread.getId());
+ }
}
return ret;
}
}
-
- protected class MonitoringThread extends Thread{
+
+ /** Monitors registered thread in order to clean up dead ones. */
+ private class MonitoringThread extends Thread {
@Override
public void run() {
- Thread thread=null;
+ while (isActive()) {
+ Iterator<Thread> it = threads.iterator();
+ while (it.hasNext()) {
+ Thread thread = it.next();
+ if (!thread.isAlive() && isActive()) {
+ if (activeSessions.containsKey(thread.getId())) {
+ Session session = activeSessions
+ .get(thread.getId());
+ activeSessions.remove(thread.getId());
+ session.logout();
+ if (log.isDebugEnabled())
+ log.debug("Cleaned up JCR session (userID="
+ + session.getUserID()
+ + ") from dead thread "
+ + thread.getId());
+ }
+ it.remove();
+ }
+ }
+
+ synchronized (ThreadBoundJcrSessionFactory.this) {
+ try {
+ ThreadBoundJcrSessionFactory.this.wait(1000);
+ } catch (InterruptedException e) {
+ // silent
+ }
+ }
+ }
}
-
+
}
}