Source code: com/opencms/flex/cache/CmsFlexCacheKey.java
1 /*
2 * File : $Source: /usr/local/cvs/opencms/src/com/opencms/flex/cache/Attic/CmsFlexCacheKey.java,v $
3 * Date : $Date: 2003/05/13 13:18:20 $
4 * Version: $Revision: 1.7.2.1 $
5 *
6 * This library is part of OpenCms -
7 * the Open Source Content Mananagement System
8 *
9 * Copyright (C) 2002 - 2003 Alkacon Software (http://www.alkacon.com)
10 *
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
15 *
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
20 *
21 * For further information about Alkacon Software, please see the
22 * company website: http://www.alkacon.com
23 *
24 * For further information about OpenCms, please see the
25 * project website: http://www.opencms.org
26 *
27 * You should have received a copy of the GNU Lesser General Public
28 * License along with this library; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30 */
31
32 package com.opencms.flex.cache;
33
34 import com.opencms.file.CmsObject;
35
36 import java.util.Iterator;
37
38 import javax.servlet.ServletRequest;
39
40 /**
41 * Implements the CmsFlexCacheKey,
42 * which is a key used to describe the caching behaviour
43 * of a specific resource.<p>
44 *
45 * It has a lot of "public" variables (which isn't good style, I know)
46 * to avoid method calling overhead (a cache is about speed, isn't it :).<p>
47 *
48 * @author Alexander Kandzior (a.kandzior@alkacon.com)
49 * @version $Revision: 1.7.2.1 $
50 */
51 public class CmsFlexCacheKey {
52
53 /** The OpenCms resource that this key is used for. */
54 public String Resource = null;
55
56 /** The cache behaviour description for the resource. */
57 public String Variation = null;
58
59 /** Cache key variable: Determines if this resource can be cached alwys, never or under certain conditions. -1 = never, 0=check, 1=always */
60 public int m_always = -1; //
61
62 /** Cache key variable: The uri of the original request */
63 public String m_uri = null;
64
65 /** Cache key variable: The user id */
66 public int m_user = -1;
67
68 /** Cache key variable: List of groups */
69 public java.util.Set m_groups = null;
70
71 /** Cache key variable: List of parameters */
72 public java.util.Map m_params = null;
73
74 /** Cache key variable: List of "blocking" parameters */
75 public java.util.Set m_noparams = null;
76
77 /** Cache key variable: Timeout of the resource */
78 public long m_timeout = -1;
79
80 /** Cache key variable: Determines if the resource sould be always cleared at publish time */
81 public boolean m_publish = false;
82
83 /** Cache key variable: Distinguishes request schemes (http, https etc.) */
84 public java.util.Set m_schemes = null;
85
86 /** Cache key variable: The request TCP/IP port */
87 public java.util.Set m_ports = null;
88
89 /** The list of keywords of the Flex cache language */
90 private java.util.List cacheCmds = java.util.Arrays.asList(new String[] {
91 "always", "never", "uri", "user", "groups", "params", "no-params", "timeout", "publish-clear", "schemes", "ports", "false", "parse-error", "true"} );
92 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13
93
94 /** Flag used to determine if this key is from a request or not */
95 private boolean m_isRequest;
96
97 /** Flag raised in case a key parse error occured */
98 private boolean m_parseError = false;
99
100 /** Debugging flag */
101 private static final boolean DEBUG = false;
102
103 /**
104 * This constructor is used when building a cache key from a request.<p>
105 *
106 * The request contains several data items that are neccessary to construct
107 * the output. These items are e.g. the Query-String, the requested resource,
108 * the current time etc. etc.
109 * All required items are saved in the constructed cache - key.<p>
110 *
111 * @param target the requested resource in the OpenCms VFS
112 * @param online must be true for an online resource, false for offline resources
113 * @param request the request to construct the key for
114 */
115 public CmsFlexCacheKey(ServletRequest request, String target, boolean online) {
116
117 Resource = getKeyName(target, online);
118 Variation = "never";
119
120 m_isRequest = true;
121 // Fetch the cms from the request
122 CmsObject cms = ((CmsFlexController)request.getAttribute(CmsFlexController.ATTRIBUTE_NAME)).getCmsObject();
123 // Get the top-level file name / uri
124 // m_uri = request.getCmsFile().getAbsolutePath();
125 m_uri = cms.getRequestContext().getUri();
126 // Fetch user from the current cms
127 m_user = cms.getRequestContext().currentUser().getId();
128 // Fetch group. Must have unique names, so the String is ok
129 m_groups = java.util.Collections.singleton(cms.getRequestContext().currentGroup().getName().toLowerCase());
130 // Get the params
131 m_params = request.getParameterMap();
132 if (m_params.size() == 0) m_params = null;
133 // No-params are null for a request key
134 m_noparams = null;
135 // Save the request time
136 m_timeout = System.currentTimeMillis();
137 // publish-clear is not related to the request
138 m_publish = false;
139 // alwalys is not related to the request
140 m_always = 0;
141 // Save the request scheme
142 m_schemes = java.util.Collections.singleton(request.getScheme().toLowerCase());
143 // Save the request port
144 m_ports = java.util.Collections.singleton(new Integer(request.getServerPort()));
145 if (DEBUG) System.err.println("Creating CmsFlexCacheKey for Request:\n" + this.toString());
146 }
147
148 /**
149 * This constructor is used when building a cache key from set of cache directives.<p>
150 *
151 * These directives are attached to the properties of the requested resource
152 * on a property called "cache".
153 * The value of this poperty that is passed in this constructor as "cacheDirectives"
154 * is parsed to build the keys data structure.<p>
155 *
156 * In case a parsing error occures, the value of this key is set to "cache=never",
157 * and the hadParseError() flag is set to true.
158 * This is done to ensure that a valid key is always constructed with the constructor.<p>
159 *
160 * @param target the requested resource
161 * @param cacheDirectives the cache directives of the resource (value of the property "cache")
162 * @param online must be true for an online resource, false for offline resources
163 */
164 public CmsFlexCacheKey(String target, String cacheDirectives, boolean online) {
165 Resource = getKeyName(target, online);
166 Variation = "never";
167 m_isRequest = false;
168 if (cacheDirectives != null) parseFlexKey(cacheDirectives);
169 if (DEBUG) System.err.println("CmsFlexCacheKey for response generated:\n" + this.toString());
170 }
171
172 /**
173 * Calculates the cache key name that is used as key in
174 * the first level of the FlexCache.<p>
175 *
176 * @param name the name of the resource
177 * @param online must be true for an online resource, false for offline resources
178 * @return fhe FlexCache key name
179 */
180 public static String getKeyName(String name, boolean online) {
181 return name + (online?CmsFlexCache.C_CACHE_ONLINESUFFIX:CmsFlexCache.C_CACHE_OFFLINESUFFIX);
182 }
183
184 /**
185 * This flag is used to indicate that a parse error had
186 * occured, which can happen if the cache directives String
187 * passed to the constructor using the response is
188 * not build according to the Flex cache language syntax.<p>
189 *
190 * @return true if a parse error did occur, false otherwise
191 */
192 public boolean hadParseError() {
193 return m_parseError;
194 }
195
196 /**
197 * Compares this key to the other key passed as parameter,
198 * from comparing the two keys, a variation String is constructed.<p>
199 *
200 * This method is the "heart" of the key matching process.<p>
201 *
202 * The assumtion is that this key should be the one constructed for the response,
203 * while the parameter key should have been constructed from the request.<p>
204 *
205 * A short example how this works:
206 * If the resource key is "cache=groups" and the request is done from a guest user
207 * (which always belongs to the default group "guests"),
208 * the constructed variation will be "groups=(guests)".<p>
209 *
210 * @param key the key to match this key with
211 * @return null if not cachable, or the Variation String if cachable
212 */
213 public String matchRequestKey(CmsFlexCacheKey key) {
214
215 StringBuffer str = new StringBuffer(100);
216 if (m_always < 0) {
217 if (DEBUG) System.err.println("keymatch: cache=never");
218 return null;
219 }
220
221 if (DEBUG) System.err.println("keymatch: Checking no-params");
222 if ((m_noparams != null) && (key.m_params != null)) {
223 if ((m_noparams.size() == 0) && (key.m_params.size() > 0)) return null;
224 Iterator i = key.m_params.keySet().iterator();
225 while (i.hasNext()) {
226 if (m_noparams.contains(i.next())) return null;
227 }
228 }
229
230 if (m_always > 0) {
231 if (DEBUG) System.err.println("keymatch: cache=always");
232 str.append("always");
233 return str.toString();
234 }
235
236 if (DEBUG) System.err.println("keymatch: Checking groups");
237 if (m_groups != null) {
238 String g = (String)key.m_groups.iterator().next();
239 if (DEBUG) System.err.println("keymatch: Request group is " + g);
240 if ((m_groups.size() > 0) && ! m_groups.contains(g)) return null;
241 str.append("groups=(");
242 str.append(g);
243 str.append(");");
244 }
245
246 if (m_uri != null) {
247 str.append("uri=(");
248 str.append(key.m_uri);
249 str.append(");");
250 }
251
252 if (m_user > 0) {
253 str.append("user=(");
254 str.append(key.m_user);
255 str.append(");");
256 }
257
258 if (m_params != null) {
259 str.append("params=(");
260 if (key.m_params != null) {
261 if (m_params.size() > 0) {
262 // Match only params listed in cache directives
263 Iterator i = m_params.keySet().iterator();
264 while (i.hasNext()) {
265 Object o = i.next();
266 if (key.m_params.containsKey(o)) {
267 str.append(o);
268 str.append("=");
269 // TODO: handle multiple occurences of the same parameter value
270 String[] values = (String[])key.m_params.get(o);
271 str.append(values[0]);
272 if (i.hasNext()) str.append(",");
273 }
274 }
275 } else {
276 // Match all request params
277 Iterator i = key.m_params.keySet().iterator();
278 while (i.hasNext()) {
279 Object o = i.next();
280 str.append(o);
281 str.append("=");
282 // TODO: handle multiple occurences of the same parameter value
283 String[] values = (String[])key.m_params.get(o);
284 str.append(values[0]);
285 if (i.hasNext()) str.append(",");
286 }
287 }
288 }
289 str.append(")");
290 }
291
292 if (m_schemes != null) {
293 String s = (String)key.m_schemes.iterator().next();
294 if ((m_schemes.size() > 0) && (! m_schemes.contains(s.toLowerCase()))) return null;
295 str.append("schemes=(");
296 str.append(s);
297 str.append(");");
298 }
299
300 if (m_ports != null) {
301 Integer i = (Integer)key.m_ports.iterator().next();
302 if ((m_ports.size() > 0) && (! m_ports.contains(i))) return null;
303 str.append("ports=(");
304 str.append(i);
305 str.append(");");
306 }
307
308 if (m_timeout > 0) {
309 str.append("timeout=(");
310 str.append(m_timeout);
311 str.append(");");
312 }
313
314 return str.toString();
315 }
316
317 /**
318 * @see java.lang.Object#toString()
319 *
320 * @return a complete String representation for this key
321 */
322 public String toString() {
323 StringBuffer str = new StringBuffer(100);
324
325 if (m_always < 0) {
326 str.append("never");
327 if (m_parseError) {
328 str.append(";parse-error");
329 }
330 return str.toString();
331 }
332 if (m_noparams != null) {
333 // Add "no-cachable" parameters
334 if (m_noparams.size() == 0) {
335 str.append("no-params;");
336 } else {
337 str.append("no-params=(");
338 Iterator i = m_noparams.iterator();
339 while (i.hasNext()) {
340 Object o = i.next();
341 str.append(o);
342 if (i.hasNext()) str.append(",");
343 }
344 str.append(");");
345 }
346 }
347 if (m_always > 0) {
348 str.append("always");
349 if (m_parseError) {
350 str.append(";parse-error");
351 }
352 return str.toString();
353 }
354 if (m_uri != null) {
355 if (m_uri.equals("uri")) {
356 str.append("uri;");
357 } else {
358 str.append("uri=(");
359 str.append(m_uri);
360 str.append(");");
361 }
362 }
363 if (m_user >= 0) {
364 // Add user data
365 if (m_user == Integer.MAX_VALUE) {
366 str.append("user;");
367 } else {
368 str.append("user=(");
369 str.append(m_user);
370 str.append(");");
371 }
372 }
373 if (m_groups != null) {
374 // Add group data
375 if (m_groups.size() == 0) {
376 str.append("groups;");
377 } else {
378 str.append("groups=(");
379 Iterator i = m_groups.iterator();
380 while (i.hasNext()) {
381 str.append(i.next());
382 if (i.hasNext()) str.append(",");
383 }
384 str.append(");");
385 }
386 }
387 if (m_params != null) {
388 // Add parameters
389 if (m_params.size() == 0) {
390 str.append("params;");
391 } else {
392 str.append("params=(");
393 Iterator i = m_params.keySet().iterator();
394 while (i.hasNext()) {
395 Object o = i.next();
396 str.append(o);
397 try {
398 // TODO: handle multiple occurences of the same parameter value
399 String param[] = (String[])m_params.get(o);
400 if (! "&?&".equals(param[0])) {
401 str.append("=");
402 str.append(param[0]);
403 }
404 } catch(Exception e) {
405 if (DEBUG) System.err.println("Exception! o=" + o + " Exception is " + e );
406 }
407 if (i.hasNext()) str.append(",");
408 }
409 str.append(");");
410 }
411 }
412 if (m_timeout >= 0) {
413 // Add timeout
414 str.append("timeout=(");
415 str.append(m_timeout);
416 str.append(");");
417 }
418 if (m_publish) {
419 // Add publish parameters
420 str.append("publish-clear;");
421 }
422 if (m_schemes != null) {
423 // Add schemes
424 if (m_schemes.size() == 0) {
425 str.append("schemes;");
426 } else {
427 str.append("schemes=(");
428 Iterator i = m_schemes.iterator();
429 while (i.hasNext()) {
430 str.append(i.next());
431 if (i.hasNext()) str.append(",");
432 }
433 str.append(");");
434 }
435 }
436 if (m_ports != null) {
437 // Add ports
438 if (m_ports.size() == 0) {
439 str.append("ports;");
440 } else {
441 str.append("ports=(");
442 Iterator i = m_ports.iterator();
443 while (i.hasNext()) {
444 str.append(i.next());
445 if (i.hasNext()) str.append(",");
446 }
447 str.append(");");
448 }
449 }
450
451 if (m_parseError) {
452 str.append("parse-error;");
453 }
454 return str.toString();
455 }
456
457 /**
458 * Parse a String in the Flex cache language and construct
459 * the key data structure from this.<p>
460 *
461 * @param key the String to parse (usually read from the file property "cache")
462 */
463 private void parseFlexKey(String key) {
464 java.util.StringTokenizer toker = new java.util.StringTokenizer(key,";");
465 try {
466 while (toker.hasMoreElements()) {
467 String t = toker.nextToken();
468 String k = null;
469 String v = null;
470 int idx = t.indexOf("=");
471 if (idx >= 0) {
472 k = t.substring(0, idx).trim();
473 if (t.length() > idx) v = t.substring(idx+1).trim();
474 } else {
475 k = t.trim();
476 }
477 m_always = 0;
478 if (DEBUG) System.err.println("Parsing token:" + t + " key=" + k + " value=" + v);
479 switch (cacheCmds.indexOf(k)) {
480 case 0: // always
481 case 13:
482 m_always = 1;
483 // Continue processing (make sure we find a "never" behind "always")
484 break;
485 case 1: // never
486 case 11:
487 m_always = -1;
488 // No need for any further processing
489 return;
490 case 2: // uri
491 m_uri = "uri";
492 // being != null is enough
493 break;
494 case 3: // user
495 m_user = Integer.MAX_VALUE;
496 // being > 0 is enough
497 break;
498 case 4: // groups
499 if (v != null) {
500 // A list of groups is present
501 m_groups = parseValueMap(v).keySet();
502 } else {
503 // Cache all groups
504 m_groups = new java.util.HashSet(0);
505 }
506 break;
507 case 5: // params
508 m_params = parseValueMap(v);
509 break;
510 case 6: // no-params
511 if (v != null) {
512 // No-params are present
513 m_noparams = parseValueMap(v).keySet();
514 } else {
515 // Never cache with parameters
516 m_noparams = new java.util.HashSet(0);
517 }
518 break;
519 case 7: // timeout
520 m_timeout = Integer.parseInt(v);
521 break;
522 case 8: // publish
523 m_publish = true;
524 break;
525 case 9: // schemes
526 m_schemes = parseValueMap(v).keySet();
527 break;
528 case 10: // ports
529 m_ports = parseValueMap(v).keySet();
530 break;
531 case 12: // previous parse error - ignore
532 break;
533 default: // unknown directive, throw error
534 m_parseError = true;
535 }
536 }
537 } catch (Exception e) {
538 // Any Exception here indicates a parsing error
539 if (DEBUG) System.err.println("----- Error in key parsing: " + e.toString());
540 m_parseError = true;
541 }
542 if (m_parseError) {
543 // If string is invalid set cache to "never"
544 m_always = -1;
545 }
546 }
547
548 /**
549 * A helper method for the parsing process which parses
550 * Strings like groups=(a, b, c).<p>
551 *
552 * @param value the String to parse
553 * @return a Map that contains of the parsed values, only the keyset of the Map is needed later
554 */
555 private java.util.Map parseValueMap(String value) {
556 if (value.charAt(0) == '(') value = value.substring(1);
557 int len;
558 if (value.charAt(len = (value.length()-1)) == ')') value = value.substring(0, len);
559 if (value.charAt(len-1) == ',') value = value.substring(0, len-1);
560 if (DEBUG) System.err.println("Parsing map: " + value);
561 java.util.StringTokenizer toker = new java.util.StringTokenizer(value, ",");
562 java.util.Map result = new java.util.HashMap();
563 while (toker.hasMoreTokens()) {
564 result.put(toker.nextToken().trim(), new String[] { "&?&" } );
565 }
566 return result;
567 }
568
569 }