001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2008
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.shared;
034:
035: import com.flexive.shared.configuration.DivisionData;
036: import com.flexive.shared.exceptions.FxAccountInUseException;
037: import com.flexive.shared.exceptions.FxApplicationException;
038: import com.flexive.shared.exceptions.FxLoginFailedException;
039: import com.flexive.shared.exceptions.FxLogoutFailedException;
040: import com.flexive.shared.interfaces.AccountEngine;
041: import com.flexive.shared.security.UserTicket;
042: import org.apache.commons.logging.Log;
043: import org.apache.commons.logging.LogFactory;
044:
045: import javax.servlet.http.HttpServletRequest;
046: import javax.servlet.http.HttpSession;
047: import java.io.Serializable;
048: import java.net.URLDecoder;
049: import java.util.Locale;
050:
051: /**
052: * The [fleXive] context - user session specific data like UserTickets, etc.
053: *
054: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
055: * @author Gregor Schober (gregor.schober@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
056: * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
057: */
058: public class FxContext implements Serializable {
059: private static final long serialVersionUID = -54895743895893486L;
060:
061: /**
062: * Global division id
063: */
064: public final static int DIV_GLOBAL_CONFIGURATION = 0;
065: /**
066: * Undefined division id
067: */
068: public final static int DIV_UNDEFINED = -1;
069: /**
070: * Session key set if the user successfully logged into the admin area
071: */
072: public static final String ADMIN_AUTHENTICATED = "$flexive_admin_auth$";
073:
074: private static final transient Log LOG = LogFactory
075: .getLog(FxContext.class);
076: private static ThreadLocal<FxContext> info = new ThreadLocal<FxContext>() {
077: };
078:
079: private final String sessionID;
080: private final String requestURI;
081: private final String remoteHost;
082: private final boolean webDAV;
083: private final String serverName;
084: private final int serverPort;
085:
086: private boolean treeWasModified;
087: private String contextPath;
088: private String requestUriNoContext;
089: private boolean globalAuthenticated;
090: private int division;
091: private int runAsSystem;
092: private UserTicket ticket;
093: private long nodeId = -1;
094:
095: protected static UserTicket getLastUserTicket(HttpSession session) {
096: return (UserTicket) session.getAttribute("LAST_USERTICKET");
097: }
098:
099: protected static void setLastUserTicket(HttpSession session,
100: UserTicket lastUserTicket) {
101: session.setAttribute("LAST_USERTICKET", lastUserTicket);
102: }
103:
104: public UserTicket getTicket() {
105: if (getRunAsSystem()) {
106: return ticket.cloneAsGlobalSupervisor();
107: }
108: return ticket;
109: }
110:
111: public void setTicket(UserTicket ticket) {
112: this .ticket = ticket;
113: }
114:
115: /**
116: * Get the current users active tree node id (CMS extension)
117: *
118: * @return the current users active tree node id
119: */
120: public long getNodeId() {
121: return nodeId;
122: }
123:
124: /**
125: * Set the current users active tree node id (CMS extension)
126: *
127: * @param nodeId active tree node id
128: */
129: public void setNodeId(long nodeId) {
130: this .nodeId = nodeId;
131: }
132:
133: /**
134: * Returns true if the tree was modified within this thread by the
135: * user belonging to this thread.
136: *
137: * @return true if the tree was modified
138: */
139: public boolean getTreeWasModified() {
140: return treeWasModified;
141: }
142:
143: /**
144: * Flag the tree as modified
145: */
146: public void setTreeWasModified() {
147: this .treeWasModified = true;
148: }
149:
150: /**
151: * Get the current users preferred locale (based on his preferred language)
152: *
153: * @return the current users preferred locale (based on his preferred language)
154: */
155: public Locale getLocale() {
156: return ticket.getLanguage().getLocale();
157: }
158:
159: /**
160: * Get the current users preferred language
161: *
162: * @return the current users preferred language
163: */
164: public FxLanguage getLanguage() {
165: return ticket.getLanguage();
166: }
167:
168: /**
169: * Tries to login a user.
170: * <p/>
171: * The next getUserTicket() call will return the new ticket.
172: *
173: * @param loginname the unique user name
174: * @param password the password
175: * @param takeOver the take over flag
176: * @throws FxLoginFailedException if the login failed
177: * @throws FxAccountInUseException if take over was false and the account is in use
178: */
179: public void login(String loginname, String password,
180: boolean takeOver) throws FxLoginFailedException,
181: FxAccountInUseException {
182: // Anything to do at all?
183: if (ticket != null && ticket.getLoginName().equals(loginname)) {
184: return;
185: }
186: // Try the login
187: AccountEngine acc = EJBLookup.getAccountEngine();
188: acc.login(loginname, password, takeOver);
189: ticket = acc.getUserTicket();
190: }
191:
192: /**
193: * Logout of the current user.
194: *
195: * @throws FxLogoutFailedException if the function fails
196: */
197: public void logout() throws FxLogoutFailedException {
198: AccountEngine acc = EJBLookup.getAccountEngine();
199: acc.logout();
200: ticket = acc.getUserTicket();
201: }
202:
203: /**
204: * Override the used ticket.
205: * Please do not use this method! Its only purpose is to feed FxContext with a
206: * USerTicket when no user is logged in - ie during system startup
207: *
208: * @param ticket ticket to override with
209: */
210: public void overrideTicket(UserTicket ticket) {
211: this .ticket = ticket;
212: }
213:
214: /**
215: * Constructor
216: *
217: * @param request the request
218: * @param divisionId the division
219: * @param isWebdav true if this is an webdav request
220: */
221: private FxContext(HttpServletRequest request, int divisionId,
222: boolean isWebdav) {
223: this .sessionID = request.getSession().getId();
224: this .requestURI = request.getRequestURI();
225: this .contextPath = request.getContextPath();
226: this .serverName = request.getServerName();
227: this .serverPort = request.getServerPort();
228: this .requestUriNoContext = request.getRequestURI().substring(
229: request.getContextPath().length());
230: this .webDAV = isWebdav;
231: if (this .webDAV) {
232: // Cut away servlet path, eg. "/webdav/"
233: this .requestUriNoContext = this .requestUriNoContext
234: .substring(request.getServletPath().length());
235: }
236: this .globalAuthenticated = request.getSession().getAttribute(
237: ADMIN_AUTHENTICATED) != null;
238: this .remoteHost = request.getRemoteHost();
239: this .division = divisionId;
240: }
241:
242: /**
243: * Gets the user ticket from the ejb layer, and stores it in the session as 'last used user ticket'
244: *
245: * @param session the session
246: * @return the user ticket
247: */
248: private static UserTicket getTicketFromEJB(final HttpSession session) {
249: try {
250: UserTicket ticket = EJBLookup.getAccountEngine()
251: .getUserTicket();
252: setLastUserTicket(session, ticket);
253: return ticket;
254: } catch (Exception e) {
255: System.err.println("Failed: " + e.getMessage() + "<br>");
256: e.printStackTrace();
257: return null;
258: }
259: }
260:
261: /**
262: * Constructor
263: */
264: private FxContext() {
265: sessionID = "EJB_" + System.currentTimeMillis();
266: requestURI = "";
267: division = -1;
268: remoteHost = "127.0.0.1 (SYSTEM)";
269: webDAV = false;
270: serverName = "localhost";
271: serverPort = 80;
272: }
273:
274: /**
275: * Returns true if the division is the global configuration division.
276: *
277: * @return true if the division is the global configuration division
278: */
279: public boolean isGlobalConfigDivision() {
280: return division == DivisionData.DIVISION_GLOBAL;
281: }
282:
283: /**
284: * Return true if the current context runs in the test division.
285: *
286: * @return true if the current context runs in the test division.
287: */
288: public boolean isTestDivision() {
289: return division == DivisionData.DIVISION_TEST;
290: }
291:
292: /**
293: * Returns the id of the division.
294: * <p/>
295: *
296: * @return the id of the division.
297: */
298: public int getDivisionId() {
299: return this .division;
300: }
301:
302: /**
303: * Changes the divison ID. Use with care!
304: * (Currently needed for embedded container testing.)
305: *
306: * @param division the division id
307: */
308: public void setDivisionId(int division) {
309: this .division = division;
310: }
311:
312: /**
313: * Changes the context path (Currently needed for embedded container testing.)
314: *
315: * @param contextPath the context path
316: */
317: public void setContextPath(String contextPath) {
318: this .contextPath = contextPath;
319: }
320:
321: /**
322: * Runs all further calls as SYSTEM user with full permissions until stopRunAsSystem
323: * gets called. Multiple calls to this function get stacked and the runAsSystem
324: * flag is only removed when the stack is empty.
325: */
326: public void runAsSystem() {
327: runAsSystem++;
328: }
329:
330: /**
331: * Removes one runeAsSystem flag from the stack.
332: */
333: public void stopRunAsSystem() {
334: if (runAsSystem <= 0) {
335: LOG
336: .fatal("stopRunAsSystem called with no system flag on the stack");
337: } else
338: runAsSystem--;
339: }
340:
341: /**
342: * Returns true if all calls are done without permission checks for the time beeing.
343: *
344: * @return true if all calls are done without permission checks for the time beeing
345: */
346: public boolean getRunAsSystem() {
347: return runAsSystem != 0;
348: }
349:
350: /**
351: * Returns the session id, which is unique at call time
352: *
353: * @return the session's id
354: */
355: public String getSessionId() {
356: return sessionID;
357: }
358:
359: /**
360: * Returns the request URI.
361: * <p/>
362: * This URI contains the context path, use getRelativeRequestURI() to retrieve the path
363: * without it.
364: *
365: * @return the request URI
366: */
367: public String getRequestURI() {
368: return requestURI;
369: }
370:
371: /**
372: * Returns the decoded relative request URI.
373: * <p/>
374: * This function is the same as calling getRelativeRequestURI(true).
375: *
376: * @return the URI without its context path
377: */
378: public String getRelativeRequestURI() {
379: return getRelativeRequestURI(true);
380: }
381:
382: /**
383: * Returns the relative request URI.
384: *
385: * @param decode if set to true the URI will be decoded (eg "%20" to a space), using UTF-8
386: * @return the URI without its context path
387: */
388: @SuppressWarnings("deprecation")
389: public String getRelativeRequestURI(boolean decode) {
390: String result = requestURI.substring(contextPath.length());
391: if (decode) {
392: try {
393: result = URLDecoder.decode(result, "UTF-8");
394: } catch (Throwable t) {
395: System.out
396: .print("Failed to decode the URI using UTF-8, using fallback decoding. msg="
397: + t.getMessage());
398: result = URLDecoder.decode(result);
399: }
400: }
401: return result;
402: }
403:
404: /**
405: * Returns the name of the server handling this request, e.g. www.flexive.com
406: *
407: * @return the name of the server handling this request, e.g. www.flexive.com
408: */
409: public String getServerName() {
410: return serverName;
411: }
412:
413: /**
414: * Returns the port of the server handling this request, e.g. 80
415: *
416: * @return the port of the server handling this request, e.g. 80
417: */
418: public int getServerPort() {
419: return serverPort;
420: }
421:
422: /**
423: * Returns the full server URL including the port for this request, e.g. http://www.flexive.com:8080
424: *
425: * @return the full server URL including the port for this request, e.g. http://www.flexive.com:8080
426: */
427: public String getServer() {
428: return "http://" + serverName
429: + (serverPort != 80 ? ":" + serverPort : "");
430: }
431:
432: /**
433: * Returns the calling remote host.
434: *
435: * @return the remote host.
436: */
437: public String getRemoteHost() {
438: return remoteHost;
439: }
440:
441: /**
442: * Returns the id of the appication the request was made in.
443: * <p/>
444: * In webapps the application id equals the context path
445: *
446: * @return the id of the appication the request was made in.
447: */
448: public String getApplicationId() {
449: return (contextPath != null && contextPath.length() > 0 && contextPath
450: .charAt(0) == '/') ? contextPath.substring(1)
451: : contextPath;
452: }
453:
454: /**
455: * Returns the absolute path for the given resource (i.e. the application name + the path).
456: *
457: * @param path the path of the resource (e.g. /pub/css/demo.css)
458: * @return the absolute path for the given resource
459: */
460: public String getAbsolutePath(String path) {
461: return "/" + getApplicationId() + path;
462: }
463:
464: /**
465: * Gets the session information for the running thread
466: *
467: * @return the session information for the running thread
468: */
469: public static FxContext get() {
470: FxContext result = info.get();
471: if (result == null) {
472: result = new FxContext();
473: info.set(result);
474: }
475: return result;
476: }
477:
478: /**
479: * Reload the UserTicket, needed i.e. when language settings change
480: */
481: public void _reloadUserTicket() {
482: ticket = EJBLookup.getAccountEngine().getUserTicket();
483: }
484:
485: /**
486: * Returns true if this request is triggered by a webdav operation.
487: *
488: * @return true if this request is triggered by a webdav operation
489: */
490: public boolean isWebDAV() {
491: return webDAV;
492: }
493:
494: /**
495: * Return true if the user successfully authenticated for the
496: * global configuration area
497: *
498: * @return true if the user successfully authenticated for the global configuration
499: */
500: public boolean isGlobalAuthenticated() {
501: return globalAuthenticated;
502: }
503:
504: /**
505: * Authorize the user for the global configuration area
506: *
507: * @param globalAuthenticated true if the user should be authorized for the global configuration
508: */
509: public void setGlobalAuthenticated(boolean globalAuthenticated) {
510: this .globalAuthenticated = globalAuthenticated;
511: }
512:
513: /**
514: * Returns the request URI without its context.
515: *
516: * @return the request URI without its context.
517: */
518: public String getRequestUriNoContext() {
519: return requestUriNoContext;
520: }
521:
522: /**
523: * Return the context path of this request.
524: *
525: * @return the context path of this request.
526: */
527: public String getContextPath() {
528: return contextPath;
529: }
530:
531: /**
532: * Stores the needed informations about the sessions.
533: *
534: * @param request the users request
535: * @param dynamicContent is the content dynamic?
536: * @param divisionId the division id
537: * @param isWebdav true if this is an webdav request
538: * @return FxContext
539: */
540: public static FxContext storeInfos(HttpServletRequest request,
541: boolean dynamicContent, int divisionId, boolean isWebdav) {
542: FxContext si = new FxContext(request, divisionId, isWebdav);
543: // Set basic informations needed for the user ticket retrieval
544: info.set(si);
545: // Do user ticket retrieval and store it in the threadlocal
546: final HttpSession session = request.getSession();
547: if (dynamicContent || isWebdav) {
548: UserTicket last = getLastUserTicket(session);
549: // Always determine the current user ticket for dynamic pages and webdav requests.
550: // This takes about 1 x 5ms for every request on a development machine
551: si.ticket = getTicketFromEJB(session);
552: if (si.ticket.isGuest()) {
553: try {
554: if (last == null)
555: si.ticket.overrideLanguage(EJBLookup
556: .getLanguageEngine().load(
557: request.getLocale()
558: .getLanguage()));
559: else
560: si.ticket.overrideLanguage(last.getLanguage());
561: } catch (FxApplicationException e) {
562: if (LOG.isInfoEnabled()) {
563: LOG.info(
564: "Failed to use request locale from browser: "
565: + e.getMessage(), e);
566: }
567: }
568: }
569: } else {
570: // For static content like images we use the last user ticket stored in the session
571: // to speed up the request.
572: si.ticket = getLastUserTicket(session);
573: if (si.ticket != null) {
574: if (si.ticket.isGuest()
575: && (si.ticket.getACLAssignments() == null || si.ticket
576: .getACLAssignments().length == 0)) {
577: //reload from EJB layer if we have a guest ticket with no ACL assignments
578: //this can happen during initial loading
579: si.ticket = getTicketFromEJB(session);
580: }
581: }
582: if (si.ticket == null) {
583: si.ticket = getTicketFromEJB(session);
584: }
585: }
586: info.set(si);
587: return si;
588: }
589:
590: /**
591: * Performs a cleanup of the stored informations.
592: */
593: public static void cleanup() {
594: if (info.get() != null) {
595: info.remove();
596: }
597: }
598:
599: /**
600: * Returns a string representation of the object.
601: *
602: * @return a string representation of the object.
603: */
604: public String toString() {
605: return this .getClass() + "[sessionId:" + sessionID
606: + ";requestUri:" + requestURI + "]";
607: }
608:
609: }
|