001: /******************************************************************************
002: * JBoss, a division of Red Hat *
003: * Copyright 2006, Red Hat Middleware, LLC, and individual *
004: * contributors as indicated by the @authors tag. See the *
005: * copyright.txt in the distribution for a full listing of *
006: * individual contributors. *
007: * *
008: * This is free software; you can redistribute it and/or modify it *
009: * under the terms of the GNU Lesser General Public License as *
010: * published by the Free Software Foundation; either version 2.1 of *
011: * the License, or (at your option) any later version. *
012: * *
013: * This software is distributed in the hope that it will be useful, *
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of *
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
016: * Lesser General Public License for more details. *
017: * *
018: * You should have received a copy of the GNU Lesser General Public *
019: * License along with this software; if not, write to the Free *
020: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *
021: * 02110-1301 USA, or see the FSF site: http://www.fsf.org. *
022: ******************************************************************************/package org.jboss.portal.core.aspects.server;
023:
024: import org.jboss.logging.Logger;
025: import org.jboss.portal.common.invocation.AttributeResolver;
026: import org.jboss.portal.common.invocation.InvocationException;
027: import org.jboss.portal.core.CoreConstants;
028: import org.jboss.portal.identity.CachedUserImpl;
029: import org.jboss.portal.identity.NoSuchUserException;
030: import org.jboss.portal.identity.User;
031: import org.jboss.portal.identity.UserModule;
032: import org.jboss.portal.identity.UserProfileModule;
033: import org.jboss.portal.server.ServerInterceptor;
034: import org.jboss.portal.server.ServerInvocation;
035:
036: import javax.naming.InitialContext;
037: import javax.naming.NameNotFoundException;
038: import javax.naming.NamingException;
039: import javax.servlet.http.HttpServletRequest;
040: import javax.servlet.http.HttpSession;
041: import javax.servlet.http.HttpSessionBindingEvent;
042: import javax.servlet.http.HttpSessionBindingListener;
043: import javax.transaction.UserTransaction;
044: import javax.transaction.Transaction;
045: import java.io.Serializable;
046: import java.security.Principal;
047: import java.util.Date;
048: import java.util.HashMap;
049: import java.util.Map;
050:
051: /**
052: * The interceptor is responsible for managing the user identity lifecycle based on the principal name returned by the
053: * <code>HttpServletRequest.getUserPrincipal()</code> method.
054: * <p/>
055: * It manages also the user finalization that will set the last login date to the current value when the user is not
056: * used anymore.
057: *
058: * @author <a href="mailto:julien@jboss.org">Julien Viet</a>
059: * @version $Revision: 9201 $
060: */
061: public class UserInterceptor extends ServerInterceptor {
062:
063: /** . */
064: public static final String PROFILE_KEY = "profile";
065:
066: /** . */
067: public static final String USER_KEY = "user";
068:
069: /** Our logger. */
070: private static final Logger log = Logger
071: .getLogger(UserInterceptor.class);
072:
073: /** User. */
074: protected UserModule userModule = null;
075:
076: /** UserProfile */
077: protected UserProfileModule userProfileModule = null;
078:
079: /** . */
080: protected boolean cacheUser = true;
081:
082: public UserModule getUserModule() {
083: if (userModule == null) {
084: try {
085: userModule = (UserModule) new InitialContext()
086: .lookup("java:portal/UserModule");
087: } catch (NamingException e) {
088: log.error("could not obtain User Module: ", e);
089: }
090: }
091: return userModule;
092: }
093:
094: public UserProfileModule getUserProfileModule() {
095: if (userProfileModule == null) {
096: try {
097: userProfileModule = (UserProfileModule) new InitialContext()
098: .lookup("java:portal/UserProfileModule");
099: } catch (NamingException e) {
100: log.error("could not obtain UserProfileModule: ", e);
101: }
102: }
103: return userProfileModule;
104: }
105:
106: public void setUserModule(UserModule userModule) {
107: this .userModule = userModule;
108: }
109:
110: protected void invoke(ServerInvocation invocation)
111: throws Exception, InvocationException {
112: boolean trace = log.isTraceEnabled();
113: HttpServletRequest req = invocation.getServerContext()
114: .getClientRequest();
115: HttpSession httpSession = req.getSession();
116:
117: // Get scope
118: AttributeResolver principalScopeResolver = invocation
119: .getContext().getAttributeResolver(
120: ServerInvocation.PRINCIPAL_SCOPE);
121:
122: // Get the id
123: Principal userPrincipal = req.getUserPrincipal();
124:
125: // The user and its profile
126: User user = null;
127: Map profile = null;
128:
129: // Fetch user if we can
130: if (userPrincipal != null) {
131: String userName = userPrincipal.getName();
132:
133: //
134: try {
135: if (trace) {
136: log.trace("About to fetch user=" + userName);
137: }
138:
139: // Try to obtain cached user
140: user = (User) principalScopeResolver
141: .getAttribute(USER_KEY);
142:
143: //
144: if (user == null) {
145: // Fetch user info
146: user = getUserModule().findUserByUserName(userName);
147:
148: // Get profile
149: profile = getUserProfileModule()
150: .getProperties(user);
151:
152: // Build detached pojo
153: user = new CachedUserImpl(user.getId(), user
154: .getUserName());
155:
156: // Cache
157: invocation.getContext().setAttribute(
158: ServerInvocation.PRINCIPAL_SCOPE, USER_KEY,
159: user);
160:
161: // Get a detached object
162: profile = new HashMap(profile);
163:
164: // Cache
165: invocation.getContext().setAttribute(
166: ServerInvocation.PRINCIPAL_SCOPE,
167: PROFILE_KEY, profile);
168: }
169:
170: //
171: if (trace) {
172: log.trace("Found user=" + userName);
173: }
174: } catch (NoSuchUserException e) {
175: if (trace) {
176: log.trace("User not found " + userName
177: + " for principal " + userName
178: + ", will use no user instead");
179: }
180: } catch (Exception e) {
181: log.error("Cannot retrieve user=" + userName, e);
182: throw new InvocationException("Cannot fetch user="
183: + userName, e);
184: }
185: }
186:
187: //
188: if (user == null) {
189: // Erase any previous user finalizer, the side effect of unbinding the session attribute will finalize the previous user
190: UserFinalizer finalizer = (UserFinalizer) httpSession
191: .getAttribute(CoreConstants.Servlet.Session.USER_FINALIZER);
192: if (finalizer != null) {
193: if (trace) {
194: log.trace("Removing finalizer for user="
195: + finalizer.userId);
196: }
197: httpSession.setAttribute(
198: CoreConstants.Servlet.Session.USER_FINALIZER,
199: null);
200: }
201: } else {
202: // Get the current finalizer if any
203: UserFinalizer finalizer = (UserFinalizer) httpSession
204: .getAttribute(CoreConstants.Servlet.Session.USER_FINALIZER);
205:
206: // Get the user id from the request user
207: if (finalizer == null) {
208: if (trace) {
209: log.trace("Adding finalizer for user="
210: + user.getUserName());
211: }
212:
213: // Set the user finalizer
214: finalizer = new UserFinalizer(user.getId());
215:
216: // Bind the new one, the side effect finalizes the previous user
217: httpSession.setAttribute(
218: CoreConstants.Servlet.Session.USER_FINALIZER,
219: finalizer);
220: } else if (!finalizer.userId.equals(user.getId())) {
221: if (trace) {
222: log.trace("Replacing the finalizer for user="
223: + finalizer.userId + " with user="
224: + user.getId());
225: }
226:
227: // Set the user finalizer
228: finalizer = new UserFinalizer(user.getId());
229:
230: // Bind the new one, the side effect finalizes the previous user
231: httpSession.setAttribute(
232: CoreConstants.Servlet.Session.USER_FINALIZER,
233: finalizer);
234: }
235: }
236:
237: try {
238: // Continue the invocation
239: invocation.invokeNext();
240: } finally {
241: if (!cacheUser) {
242: principalScopeResolver.setAttribute(USER_KEY, null);
243: principalScopeResolver.setAttribute(PROFILE_KEY, null);
244: }
245: }
246: }
247:
248: public boolean isCacheUser() {
249: return cacheUser;
250: }
251:
252: public void setCacheUser(boolean cacheUser) {
253: this .cacheUser = cacheUser;
254: }
255:
256: /** Implement the last login date feature. */
257: private static class UserFinalizer implements Serializable,
258: HttpSessionBindingListener {
259:
260: /** The serialVersionUID */
261: private static final long serialVersionUID = -8534276945533635226L;
262:
263: private static final Logger log = Logger
264: .getLogger(UserFinalizer.class);
265:
266: private final Object userId;
267:
268: public UserFinalizer(Object userId) {
269: if (userId == null) {
270: throw new IllegalArgumentException("No user id present");
271: }
272: this .userId = userId;
273: }
274:
275: public void valueBound(HttpSessionBindingEvent event) {
276: }
277:
278: public void valueUnbound(HttpSessionBindingEvent event) {
279: try {
280: finalizeUser();
281: } catch (Exception e) {
282: log
283: .warn("Cannot set last login date for user with id '"
284: + userId + "' : " + e.getMessage());
285: log
286: .debug(
287: "Failed to set user last login date (may happen during server shutdown): ",
288: e);
289: }
290: }
291:
292: protected void finalizeUser() throws Exception {
293: /**
294: * Note: Due to a possible bug in JBoss Cache Transaction propagation within a cluster,
295: * We need to spawn a separate thread with its own new transaction to perform the finalizeUser
296: * logic.
297: *
298: * Without that, it results in data deadlocks in the cluster.
299: */
300: Thread thread = new Thread(new FinalizeRunner());
301: thread.start();
302: thread.join();
303: }
304:
305: /**
306: *
307: * @author soshah
308: *
309: */
310: private class FinalizeRunner implements Runnable {
311: public void run() {
312: UserTransaction tx = null;
313: try {
314: //
315: if (log.isTraceEnabled()) {
316: log.trace("Finalizing user " + userId);
317: }
318:
319: //
320: // Here we use JNDI to locate the module as this finalizer could have been
321: // migrated in the session of another node of the cluster
322: UserModule userModule = null;
323: UserProfileModule userProfileModule = null;
324: InitialContext ctx = new InitialContext();
325: tx = (UserTransaction) ctx
326: .lookup("UserTransaction");
327: tx.begin();
328: try {
329: userModule = (UserModule) new InitialContext()
330: .lookup("java:portal/UserModule");
331: userProfileModule = (UserProfileModule) new InitialContext()
332: .lookup("java:portal/UserProfileModule");
333: } catch (NameNotFoundException ignore) {
334: // Name is not bound anymore, it could happen during a shutdown, we don't do anything
335: }
336:
337: // Get user and set last visit date to now if we can
338: if (userModule != null && userProfileModule != null) {
339: try {
340: User user = userModule.findUserById(userId);
341: //user.setLastVisitDate(new Date());
342: userProfileModule.setProperty(user,
343: User.INFO_USER_LAST_LOGIN_DATE,
344: new Date().toString());
345:
346: } catch (NoSuchUserException e) {
347: // User is not found
348: log
349: .warn("Trying to finalize non existing user "
350: + userId);
351: }
352: }
353: tx.commit();
354: } catch (Exception e) {
355: try {
356: tx.rollback();
357: } catch (Exception rbe) {
358: }
359: log.debug(this, e);
360: }
361: }
362: }
363: }
364: }
|