001: /*
002: * HistoryFilter.java
003: *
004: * Brazil project web application Framework,
005: * export version: 1.1
006: * Copyright (c) 2000 Sun Microsystems, Inc.
007: *
008: * Sun Public License Notice
009: *
010: * The contents of this file are subject to the Sun Public License Version
011: * 1.0 (the "License"). You may not use this file except in compliance with
012: * the License. A copy of the License is included as the file "license.terms",
013: * and also available at http://www.sun.com/
014: *
015: * The Original Code is from:
016: * Brazil project web application Framework release 1.1.
017: * The Initial Developer of the Original Code is: cstevens.
018: * Portions created by cstevens are Copyright (C) Sun Microsystems, Inc.
019: * All Rights Reserved.
020: *
021: * Contributor(s): cstevens, suhler.
022: *
023: * Version: 1.6
024: * Created by cstevens on 00/04/12
025: * Last modified by suhler on 00/12/11 13:31:35
026: */
027:
028: package sunlabs.brazil.proxy;
029:
030: import sunlabs.brazil.filter.Filter;
031: import sunlabs.brazil.server.Request;
032: import sunlabs.brazil.server.Server;
033: import sunlabs.brazil.session.SessionManager;
034: import sunlabs.brazil.util.http.HttpUtil;
035: import sunlabs.brazil.util.http.MimeHeaders;
036: import sunlabs.brazil.util.regexp.Regexp;
037:
038: import java.io.Serializable;
039: import java.util.Enumeration;
040: import java.util.Hashtable;
041: import java.util.Properties;
042:
043: /**
044: * The <code>HistoryFilter</code> is both a <code>Handler</code> and a
045: * <code>Filter</code> that keeps a record of all pages visited by a
046: * given session.
047: * <p>
048: * The <code>HistoryFilter</code> can be used to
049: * make a user's session "mobile" as follows: A user's history
050: * is normally stored with the browser being used, on the user's machine.
051: * If the user runs a different browser or goes to a different machine, the
052: * user's history will not be there. Instead, the user can access the web
053: * via a proxy that keeps track of their history. No matter which browser
054: * the user chooses or machine the user is at, a server running with the
055: * <code>HistoryFilter</code> will automatically remember and be able to
056: * present the user's history.
057: * <p>
058: * The history is kept with respect to a Session ID.
059: * <p>
060: * This filter uses the following configuration properties: <dl class=props>
061: *
062: * <dt> <code>prefix</code>
063: * <dd> This handler will only process URLs beginning with this string.
064: * The default value is "", which matches all URLs.
065: *
066: * <dt> <code>session</code>
067: * <dd> The name of the request property that holds the Session ID. The
068: * default value is "SessionID".
069: *
070: * <dt> <code>nosession</code>
071: * <dd> The Session ID to use if the Session ID was not specified. The
072: * default value is "common".
073: *
074: * <dt> <code>admin</code>
075: * <dd> URLs beginning with this prefix cause the <code>HistoryFilter</code>
076: * to store the history information for the current Session in the
077: * request properties
078: *
079: * <dt> <code>filter</code>
080: * <dd> If specified, then this is a <code>Regexp</code> pattern to match
081: * against the "Content-Type" of the result. Setting this also implies
082: * that the <code>HistoryFilter</code> will be invoked as a
083: * <code>Filter</code> and not a <code>Handler</code>. The default
084: * value is "", which indicates that the "Content-Type" is <b>not</b>
085: * examined and that this <code>HistoryFilter</code> will be invoked
086: * as a <code>Handler</code>.
087: * </dl>
088: *
089: * @author Colin Stevens (colin.stevens@sun.com)
090: * @version 1.6, 00/12/11
091: */
092:
093: public class HistoryFilter implements Filter {
094: private static final String URL_PREFIX = "prefix";
095: private static final String SESSION = "session";
096: private static final String NOSESSION = "nosession";
097: private static final String ADMIN = "admin";
098: private static final String FILTER = "filter";
099:
100: public String urlPrefix = "";
101: public String session = "SessionID";
102: public String nosession = "common";
103: public String admin = "";
104: public Regexp filter = null;
105:
106: String prefix;
107:
108: /**
109: * Initializes this filter by reading all its configuration properties.
110: * <p>
111: * It is an error if the <code>filter</code> is specified but malformed.
112: *
113: * @param server
114: * The HTTP server.
115: *
116: * @param prefix
117: * The configuration property prefix.
118: *
119: * @return <code>true</code> if this filter initialized successfully,
120: * <code>false</code> otherwise.
121: */
122: public boolean init(Server server, String prefix) {
123: this .prefix = prefix;
124:
125: Properties props = server.props;
126: urlPrefix = props.getProperty(prefix + URL_PREFIX, urlPrefix);
127: session = props.getProperty(prefix + SESSION, session);
128: nosession = props.getProperty(prefix + NOSESSION, nosession);
129: admin = props.getProperty(prefix + ADMIN, admin);
130: if (admin.length() == 0) {
131: admin = null;
132: }
133: try {
134: String tmp = props.getProperty(prefix + FILTER, "");
135: if (tmp.length() > 0) {
136: filter = new Regexp(tmp);
137: }
138: } catch (IllegalArgumentException e) {
139: server.log(Server.LOG_DIAGNOSTIC, prefix, e.getMessage());
140: return false;
141: }
142:
143: return true;
144: }
145:
146: /**
147: * If the <code>admin</code> prefix is seen, store the history
148: * information associated with the session in the request properties.
149: * <p>
150: * If invoked as a <code>Handler</code> and the URL matches the
151: * <code>prefix</code>, records this page's address in the history.
152: *
153: * @param request
154: * The <code>Request</code> object that represents the HTTP
155: * request.
156: *
157: * @return <code>false</code>, indicating that this <code>respond</code>
158: * method ran purely for its side effects.
159: */
160: public boolean respond(Request request) {
161: if ((admin != null) && request.url.startsWith(admin)) {
162: StringBuffer sb = new StringBuffer();
163: Properties props = request.props;
164:
165: Hashtable pages = getPages(request);
166: Enumeration keys = pages.keys();
167: for (int i = 0; keys.hasMoreElements(); i++) {
168: String url = (String) keys.nextElement();
169: PageInfo pi = (PageInfo) pages.get(url);
170: sb.append(i).append(' ');
171:
172: String pfx = prefix + i;
173: props.put(pfx + ".url", pi.url);
174: props
175: .put(pfx + ".first", HttpUtil
176: .formatTime(pi.first));
177: props.put(pfx + ".last", HttpUtil.formatTime(pi.last));
178: props.put(pfx + ".count", Integer.toString(pi.count));
179: }
180: props.put(prefix + "pages", sb.toString());
181: } else if ((filter == null)
182: && request.url.startsWith(urlPrefix)) {
183: /*
184: * If no Content-Type filter is present, it means that the
185: * HistoryFilter is being used as a Handler.
186: */
187: recordUrl(request);
188: }
189:
190: return false;
191: }
192:
193: /**
194: * Called when invoked as a <code>Filter</code>. If the URL matches the
195: * <code>prefix</code> and the returned "Content-Type" matches the
196: * <code>filter</code>, records this page's address in the history.
197: *
198: * @param request
199: * The in-progress HTTP request.
200: *
201: * @param headers
202: * The MIME headers from the result.
203: *
204: * @return <code>false</code> indicating that this <code>Filter</code>
205: * does not want to modify the content.
206: */
207: public boolean shouldFilter(Request request, MimeHeaders headers) {
208: try {
209: String type = headers.get("Content-Type");
210: if (request.url.startsWith(urlPrefix)
211: && (filter.match(type) != null)) {
212: recordUrl(request);
213: }
214: } catch (Exception e) {
215: /* Ignore:
216: * No content-type
217: * Missing/malformed filter.
218: */
219: }
220: return false;
221: }
222:
223: /**
224: * Returns the original content, since this filter does not change
225: * content. Won't actually be invoked.
226: */
227: public byte[] filter(Request request, MimeHeaders headers,
228: byte[] content) {
229: return content;
230: }
231:
232: private void recordUrl(Request request) {
233: Hashtable pages = getPages(request);
234: PageInfo pi = (PageInfo) pages.get(request.url);
235: if (pi == null) {
236: pi = new PageInfo(request.url);
237: pages.put(request.url, pi);
238: }
239: pi.count++;
240: pi.last = System.currentTimeMillis();
241: }
242:
243: private Hashtable getPages(Request request) {
244: String id = request.props.getProperty(session, nosession);
245: return (Hashtable) SessionManager.getSession(id,
246: HistoryFilter.class, Hashtable.class);
247: }
248:
249: /**
250: * Keep information about a visited URL
251: */
252:
253: static class PageInfo implements Serializable {
254: String url;
255: int count;
256: long first;
257: long last;
258:
259: public PageInfo(String url) {
260: this.url = url;
261:
262: first = System.currentTimeMillis();
263: }
264: }
265: }
|