001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 * 
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.flex;
029
030import org.opencms.loader.I_CmsResourceLoader;
031import org.opencms.main.CmsLog;
032import org.opencms.util.CmsStringUtil;
033
034import java.util.Arrays;
035import java.util.Collections;
036import java.util.HashSet;
037import java.util.Iterator;
038import java.util.List;
039import java.util.Map;
040import java.util.Set;
041
042import javax.servlet.http.HttpSession;
043
044import org.apache.commons.logging.Log;
045
046/**
047 * Key used to describe the caching behaviour of a specific resource.<p>
048 *
049 * It has a lot of variables that are directly accessed (which isn't good style, I know)
050 * to avoid method calling overhead (a cache is about speed, isn't it :).<p>
051 * 
052 * @since 6.0.0 
053 */
054public class CmsFlexCacheKey {
055
056    /** Flex cache keyword: always. */
057    private static final String CACHE_00_ALWAYS = "always";
058
059    /** Flex cache keyword: never. */
060    private static final String CACHE_01_NEVER = "never";
061
062    /** Flex cache keyword: uri. */
063    private static final String CACHE_02_URI = "uri";
064
065    /** Flex cache keyword: user. */
066    private static final String CACHE_03_USER = "user";
067
068    /** Flex cache keyword: params. */
069    private static final String CACHE_04_PARAMS = "params";
070
071    /** Flex cache keyword: no-params. */
072    private static final String CACHE_05_NO_PARAMS = "no-params";
073
074    /** Flex cache keyword: timeout. */
075    private static final String CACHE_06_TIMEOUT = "timeout";
076
077    /** Flex cache keyword: session. */
078    private static final String CACHE_07_SESSION = "session";
079
080    /** Flex cache keyword: schemes. */
081    private static final String CACHE_08_SCHEMES = "schemes";
082
083    /** Flex cache keyword: ports. */
084    private static final String CACHE_09_PORTS = "ports";
085
086    /** Flex cache keyword: false. */
087    private static final String CACHE_10_FALSE = CmsStringUtil.FALSE;
088
089    /** Flex cache keyword: parse-error. */
090    private static final String CACHE_11_PARSE_ERROR = "parse-error";
091
092    /** Flex cache keyword: true. */
093    private static final String CACHE_12_TRUE = CmsStringUtil.TRUE;
094
095    /** Flex cache keyword: ip. */
096    private static final String CACHE_13_IP = "ip";
097
098    /** Flex cache keyword: element. */
099    private static final String CACHE_14_ELEMENT = "element";
100
101    /** Flex cache keyword: locale. */
102    private static final String CACHE_15_LOCALE = "locale";
103
104    /** Flex cache keyword: encoding. */
105    private static final String CACHE_16_ENCODING = "encoding";
106
107    /** Flex cache keyword: site. */
108    private static final String CACHE_17_SITE = "site";
109
110    /** Flex cache keyword: attrs. */
111    private static final String CACHE_18_ATTRS = "attrs";
112
113    /** Flex cache keyword: no-attrs. */
114    private static final String CACHE_19_NO_ATTRS = "no-attrs";
115
116    /** Flex cache keyword: device. */
117    private static final String CACHE_20_DEVICE = "device";
118
119    /** Flex cache keyword: container-element. */
120    private static final String CACHE_22_CONTAINER_ELEMENT = "container-element";
121
122    /** The list of keywords of the Flex cache language. */
123    private static final List<String> CACHE_COMMANDS = Arrays.asList(new String[] {
124        CACHE_00_ALWAYS,
125        CACHE_01_NEVER,
126        CACHE_02_URI,
127        CACHE_03_USER,
128        CACHE_04_PARAMS,
129        CACHE_05_NO_PARAMS,
130        CACHE_06_TIMEOUT,
131        CACHE_07_SESSION,
132        CACHE_08_SCHEMES,
133        CACHE_09_PORTS,
134        CACHE_10_FALSE,
135        CACHE_11_PARSE_ERROR,
136        CACHE_12_TRUE,
137        CACHE_13_IP,
138        CACHE_14_ELEMENT,
139        CACHE_15_LOCALE,
140        CACHE_16_ENCODING,
141        CACHE_17_SITE,
142        CACHE_18_ATTRS,
143        CACHE_19_NO_ATTRS,
144        CACHE_20_DEVICE,
145        CACHE_22_CONTAINER_ELEMENT});
146
147    /** Marker to identify use of certain String key members (uri, ip etc.). */
148    private static final String IS_USED = "/ /";
149
150    /** The log object for this class. */
151    private static final Log LOG = CmsLog.getLog(CmsFlexCacheKey.class);
152
153    /** Cache key variable: Determines if this resource can be cached alwys, never or under certain conditions. -1 = never, 0=check, 1=always. */
154    private int m_always;
155
156    /** Cache key variable: List of attributes. */
157    private Set<String> m_attrs;
158
159    /** Cache key variable: The current container element. */
160    private String m_containerElement;
161
162    /** Cache key variable: The current device. */
163    private String m_device;
164
165    /** Cache key variable: The requested element. */
166    private String m_element;
167
168    /** Cache key variable: The requested encoding. */
169    private String m_encoding;
170
171    /** Cache key variable: The ip address of the request. */
172    private String m_ip;
173
174    /** Cache key variable: The requested locale. */
175    private String m_locale;
176
177    /** Cache key variable: List of "blocking" attributes. */
178    private Set<String> m_noattrs;
179
180    /** Cache key variable: List of "blocking" parameters. */
181    private Set<String> m_noparams;
182
183    /** Cache key variable: List of parameters. */
184    private Set<String> m_params;
185
186    /** Flag raised in case a key parse error occurred. */
187    private boolean m_parseError;
188
189    /** Cache key variable: The request TCP/IP port. */
190    private Set<Integer> m_ports;
191
192    /** The OpenCms resource that this key is used for. */
193    private String m_resource;
194
195    /** Cache key variable: Distinguishes request schemes (http, https etc.). */
196    private Set<String> m_schemes;
197
198    /** Cache key variable: List of session variables. */
199    private Set<String> m_session;
200
201    /** Cache key variable: The current site root. */
202    private String m_site;
203
204    /** Cache key variable: Timeout of the resource. */
205    private long m_timeout;
206
207    /** Cache key variable: The uri of the original request. */
208    private String m_uri;
209
210    /** Cache key variable: The user id. */
211    private String m_user;
212
213    /** The cache behaviour description for the resource. */
214    private String m_variation;
215
216    /**
217     * This constructor is used when building a cache key from set of cache directives.<p>
218     * 
219     * These directives are attached to the properties of the requested resource 
220     * on a property called "cache". 
221     * The value of this poperty that is passed in this constructor as "cacheDirectives" 
222     * is parsed to build the keys data structure.<p>
223     *
224     * In case a parsing error occures, the value of this key is set to "cache=never", 
225     * and the hadParseError() flag is set to true. 
226     * This is done to ensure that a valid key is always constructed with the constructor.<p>
227     * 
228     * @param resourcename the full name of the resource including site root
229     * @param cacheDirectives the cache directives of the resource (value of the property "cache")
230     * @param online must be true for an online resource, false for offline resources
231     */
232    public CmsFlexCacheKey(String resourcename, String cacheDirectives, boolean online) {
233
234        m_resource = getKeyName(resourcename, online);
235        m_variation = "never";
236        m_always = -1;
237        m_timeout = -1;
238        if (cacheDirectives != null) {
239            parseFlexKey(cacheDirectives);
240        }
241        if (LOG.isDebugEnabled()) {
242            LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_GENERATED_1, toString()));
243        }
244    }
245
246    /**
247     * Calculates the cache key name that is used as key in 
248     * the first level of the FlexCache.<p>
249     * 
250     * @param resourcename the full name of the resource including site root
251     * @param online must be true for an online resource, false for offline resources
252     *
253     * @return the FlexCache key name
254     */
255    public static String getKeyName(String resourcename, boolean online) {
256
257        return resourcename.concat(online ? CmsFlexCache.CACHE_ONLINESUFFIX : CmsFlexCache.CACHE_OFFLINESUFFIX);
258    }
259
260    /**
261     * Appends a flex cache key value to the given buffer.<p> 
262     *  
263     * @param str the buffer to append to
264     * @param key the key to append
265     * @param value the value to append
266     */
267    private static void appendKeyValue(StringBuffer str, String key, String value) {
268
269        str.append(key);
270        if (value == IS_USED) {
271            str.append(";");
272        } else {
273            str.append("=(");
274            str.append(value);
275            str.append(");");
276        }
277    }
278
279    /**
280     * This flag is used to indicate that a parse error had
281     * occurred, which can happen if the cache directives String
282     * passed to the constructor using the response is
283     * not build according to the Flex cache language syntax.<p>
284     * 
285     * @return true if a parse error did occur, false otherwise
286     */
287    public boolean hadParseError() {
288
289        return m_parseError;
290    }
291
292    /**
293     * Compares this key to the other key passed as parameter,
294     * from comparing the two keys, a variation String is constructed.<p>
295     * 
296     * This method is the "heart" of the key matching process.<p>
297     *
298     * The assumtion is that this key should be the one constructed for the response, 
299     * while the parameter key should have been constructed from the request.<p>
300     *
301     * A short example how this works:
302     * If the cache key is "cache=user" and the request is done from a guest user
303     * the constructed variation will be "user=(guest)".<p>
304     * 
305     * @param key the key to match this key with
306     * @return null if not cachable, or the Variation String if cachable
307     */
308    public String matchRequestKey(CmsFlexRequestKey key) {
309
310        StringBuffer str = new StringBuffer(100);
311        if (m_always < 0) {
312            if (LOG.isDebugEnabled()) {
313                LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_KEYMATCH_CACHE_NEVER_0));
314            }
315            return null;
316        }
317
318        if (LOG.isDebugEnabled()) {
319            LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_KEYMATCH_CHECK_NO_PARAMS_0));
320        }
321        if ((m_noparams != null) && (key.getParams() != null)) {
322            if ((m_noparams.size() == 0) && (key.getParams().size() > 0)) {
323                return null;
324            }
325            Iterator<String> i = key.getParams().keySet().iterator();
326            while (i.hasNext()) {
327                if (m_noparams.contains(i.next())) {
328                    return null;
329                }
330            }
331        }
332
333        if (LOG.isDebugEnabled()) {
334            LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_KEYMATCH_CHECK_NO_ATTRS_0));
335        }
336        if ((m_noattrs != null) && (key.getAttributes() != null)) {
337            if ((m_noattrs.size() == 0) && (key.getAttributes().size() > 0)) {
338                return null;
339            }
340            Iterator<String> i = key.getAttributes().keySet().iterator();
341            while (i.hasNext()) {
342                if (m_noattrs.contains(i.next())) {
343                    return null;
344                }
345            }
346        }
347
348        if (m_always > 0) {
349            if (LOG.isDebugEnabled()) {
350                LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_KEYMATCH_CACHE_ALWAYS_0));
351            }
352            str.append(CACHE_00_ALWAYS);
353            return str.toString();
354        }
355
356        if (m_uri != null) {
357            appendKeyValue(str, CACHE_02_URI, key.getUri());
358        }
359
360        if (m_site != null) {
361            appendKeyValue(str, CACHE_17_SITE, key.getSite());
362        }
363
364        if (m_element != null) {
365            appendKeyValue(str, CACHE_14_ELEMENT, key.getElement());
366        }
367
368        if (m_device != null) {
369            appendKeyValue(str, CACHE_20_DEVICE, key.getDevice());
370        }
371
372        if (m_containerElement != null) {
373            appendKeyValue(str, CACHE_22_CONTAINER_ELEMENT, key.getContainerElement());
374        }
375
376        if (m_locale != null) {
377            appendKeyValue(str, CACHE_15_LOCALE, key.getLocale());
378        }
379
380        if (m_encoding != null) {
381            appendKeyValue(str, CACHE_16_ENCODING, key.getEncoding());
382        }
383
384        if (m_ip != null) {
385            appendKeyValue(str, CACHE_13_IP, key.getIp());
386        }
387
388        if (m_user != null) {
389            appendKeyValue(str, CACHE_03_USER, key.getUser());
390        }
391
392        if (m_params != null) {
393            str.append(CACHE_04_PARAMS);
394            str.append("=(");
395            Map<String, String[]> keyParams = key.getParams();
396            if (keyParams != null) {
397                if (m_params.size() > 0) {
398                    // match only params listed in cache directives
399                    Iterator<String> i = m_params.iterator();
400                    while (i.hasNext()) {
401                        Object o = i.next();
402                        if (keyParams.containsKey(o)) {
403                            str.append(o);
404                            str.append("=");
405                            // TODO: handle multiple occurrences of the same parameter value
406                            String[] values = keyParams.get(o);
407                            str.append(values[0]);
408                            if (i.hasNext()) {
409                                str.append(",");
410                            }
411                        }
412                    }
413                } else {
414                    // match all request params
415                    Iterator<Map.Entry<String, String[]>> i = keyParams.entrySet().iterator();
416                    while (i.hasNext()) {
417                        Map.Entry<String, String[]> entry = i.next();
418                        str.append(entry.getKey());
419                        str.append("=");
420                        // TODO: handle multiple occurrences of the same parameter value
421                        String[] values = entry.getValue();
422                        str.append(values[0]);
423                        if (i.hasNext()) {
424                            str.append(",");
425                        }
426                    }
427                }
428            }
429            str.append(");");
430        }
431
432        if (m_attrs != null) {
433            str.append(CACHE_18_ATTRS);
434            str.append("=(");
435            Map<String, Object> keyAttrs = key.getAttributes();
436            if (keyAttrs != null) {
437                if (m_attrs.size() > 0) {
438                    // match only attributes listed in cache directives
439                    Iterator<String> i = m_attrs.iterator();
440                    while (i.hasNext()) {
441                        String s = i.next();
442                        if (keyAttrs.containsKey(s)) {
443                            str.append(s);
444                            str.append("=");
445                            Object value = keyAttrs.get(s);
446                            str.append(value);
447                            if (i.hasNext()) {
448                                str.append(",");
449                            }
450                        }
451                    }
452                } else {
453                    // match all request attributes
454                    Iterator<Map.Entry<String, Object>> i = keyAttrs.entrySet().iterator();
455                    while (i.hasNext()) {
456                        Map.Entry<String, Object> entry = i.next();
457                        str.append(entry.getKey());
458                        str.append("=");
459                        Object value = entry.getValue();
460                        str.append(value);
461                        if (i.hasNext()) {
462                            str.append(",");
463                        }
464                    }
465                }
466            }
467            str.append(");");
468        }
469
470        if (m_session != null) {
471            StringBuffer buf = new StringBuffer(32);
472            boolean found = false;
473            buf.append(CACHE_07_SESSION);
474            buf.append("=(");
475            HttpSession keySession = key.getSession();
476            if (keySession != null) {
477                // match only session attributes listed in cache directives
478                Iterator<String> i = m_session.iterator();
479                while (i.hasNext()) {
480                    String name = i.next();
481                    Object val = keySession.getAttribute(name);
482                    if (val != null) {
483                        found = true;
484                        buf.append(name);
485                        buf.append("=");
486                        buf.append(val);
487                        if (i.hasNext()) {
488                            buf.append(",");
489                        }
490                    }
491                }
492            }
493            if (found) {
494                buf.append(");");
495                str.append(buf);
496            }
497        }
498
499        if (m_schemes != null) {
500            String s = key.getScheme();
501            if ((m_schemes.size() > 0) && (!m_schemes.contains(s))) {
502                return null;
503            }
504            appendKeyValue(str, CACHE_08_SCHEMES, s);
505        }
506
507        if (m_ports != null) {
508            Integer i = key.getPort();
509            if ((m_ports.size() > 0) && (!m_ports.contains(i))) {
510                return null;
511            }
512            str.append(CACHE_09_PORTS);
513            str.append("=(");
514            str.append(i);
515            str.append(");");
516        }
517
518        if (m_timeout > 0) {
519            str.append(CACHE_06_TIMEOUT);
520            str.append("=(");
521            str.append(m_timeout);
522            str.append(");");
523        }
524
525        if (str.length() > 0) {
526            return str.toString();
527        } else {
528            return null;
529        }
530    }
531
532    /** 
533     * @see java.lang.Object#toString()
534     *
535     * @return a complete String representation for this key
536     */
537    @Override
538    public String toString() {
539
540        StringBuffer str = new StringBuffer(100);
541
542        if (m_always < 0) {
543            str.append(CACHE_01_NEVER);
544            if (m_parseError) {
545                str.append(";");
546                str.append(CACHE_11_PARSE_ERROR);
547            }
548            return str.toString();
549        }
550        if (m_noparams != null) {
551            // add "no-cachable" parameters
552            str.append(CACHE_05_NO_PARAMS);
553            if (m_noparams.size() == 0) {
554                str.append(";");
555            } else {
556                str.append("=(");
557                Iterator<String> i = m_noparams.iterator();
558                while (i.hasNext()) {
559                    Object o = i.next();
560                    str.append(o);
561                    if (i.hasNext()) {
562                        str.append(",");
563                    }
564                }
565                str.append(");");
566            }
567        }
568        if (m_noattrs != null) {
569            // add "no-cachable" attributes
570            str.append(CACHE_19_NO_ATTRS);
571            if (m_noattrs.size() == 0) {
572                str.append(";");
573            } else {
574                str.append("=(");
575                Iterator<String> i = m_noattrs.iterator();
576                while (i.hasNext()) {
577                    String s = i.next();
578                    str.append(s);
579                    if (i.hasNext()) {
580                        str.append(",");
581                    }
582                }
583                str.append(");");
584            }
585        }
586        if (m_always > 0) {
587            str.append(CACHE_00_ALWAYS);
588            if (m_parseError) {
589                str.append(";");
590                str.append(CACHE_11_PARSE_ERROR);
591            }
592            return str.toString();
593        }
594        if (m_uri != null) {
595            // add uri
596            appendKeyValue(str, CACHE_02_URI, m_uri);
597        }
598        if (m_site != null) {
599            // add site
600            appendKeyValue(str, CACHE_17_SITE, m_site);
601        }
602        if (m_element != null) {
603            // add element
604            appendKeyValue(str, CACHE_14_ELEMENT, m_element);
605        }
606        if (m_device != null) {
607            appendKeyValue(str, CACHE_20_DEVICE, m_device);
608        }
609        if (m_containerElement != null) {
610            appendKeyValue(str, CACHE_22_CONTAINER_ELEMENT, m_containerElement);
611        }
612        if (m_locale != null) {
613            // add locale
614            appendKeyValue(str, CACHE_15_LOCALE, m_locale);
615        }
616        if (m_encoding != null) {
617            // add encoding
618            appendKeyValue(str, CACHE_16_ENCODING, m_encoding);
619        }
620        if (m_ip != null) {
621            // add ip
622            appendKeyValue(str, CACHE_13_IP, m_ip);
623        }
624        if (m_user != null) {
625            // add user
626            appendKeyValue(str, CACHE_03_USER, m_user);
627        }
628        if (m_params != null) {
629            // add parameters
630            str.append(CACHE_04_PARAMS);
631            if (m_params.size() == 0) {
632                str.append(";");
633            } else {
634                str.append("=(");
635                Iterator<String> i = m_params.iterator();
636                while (i.hasNext()) {
637                    Object o = i.next();
638                    if (I_CmsResourceLoader.PARAMETER_ELEMENT.equals(o)) {
639                        continue;
640                    }
641                    str.append(o);
642                    if (i.hasNext()) {
643                        str.append(",");
644                    }
645                }
646                str.append(");");
647            }
648        }
649        if (m_attrs != null) {
650            // add attributes
651            str.append(CACHE_18_ATTRS);
652            if (m_attrs.size() == 0) {
653                str.append(";");
654            } else {
655                str.append("=(");
656                Iterator<String> i = m_attrs.iterator();
657                while (i.hasNext()) {
658                    String s = i.next();
659                    str.append(s);
660                    if (i.hasNext()) {
661                        str.append(",");
662                    }
663                }
664                str.append(");");
665            }
666        }
667        if (m_session != null) {
668            // add session variables
669            str.append(CACHE_07_SESSION);
670            str.append("=(");
671            Iterator<String> i = m_session.iterator();
672            while (i.hasNext()) {
673                Object o = i.next();
674                str.append(o);
675                if (i.hasNext()) {
676                    str.append(",");
677                }
678            }
679            str.append(");");
680        }
681        if (m_timeout >= 0) {
682            // add timeout 
683            str.append(CACHE_06_TIMEOUT);
684            str.append("=(");
685            str.append(m_timeout);
686            str.append(");");
687        }
688        if (m_schemes != null) {
689            // add schemes
690            str.append(CACHE_08_SCHEMES);
691            if (m_schemes.size() == 0) {
692                str.append(";");
693            } else {
694                str.append("=(");
695                Iterator<String> i = m_schemes.iterator();
696                while (i.hasNext()) {
697                    str.append(i.next());
698                    if (i.hasNext()) {
699                        str.append(",");
700                    }
701                }
702                str.append(");");
703            }
704        }
705        if (m_ports != null) {
706            // add ports
707            str.append(CACHE_09_PORTS);
708            if (m_ports.size() == 0) {
709                str.append(";");
710            } else {
711                str.append("=(");
712                Iterator<Integer> i = m_ports.iterator();
713                while (i.hasNext()) {
714                    str.append(i.next());
715                    if (i.hasNext()) {
716                        str.append(",");
717                    }
718                }
719                str.append(");");
720            }
721        }
722
723        if (m_parseError) {
724            str.append(CACHE_11_PARSE_ERROR);
725        }
726        return str.toString();
727    }
728
729    /**
730     * Returns the resource.<p>
731     *
732     * @return the resource
733     */
734    protected String getResource() {
735
736        return m_resource;
737    }
738
739    /**
740     * Returns the timeout.<p>
741     *
742     * @return the timeout
743     */
744    protected long getTimeout() {
745
746        return m_timeout;
747    }
748
749    /**
750     * Returns the variation.<p>
751     *
752     * @return the variation
753     */
754    protected String getVariation() {
755
756        return m_variation;
757    }
758
759    /**
760     * Sets the variation.<p>
761     *
762     * @param variation the variation to set
763     */
764    protected void setVariation(String variation) {
765
766        m_variation = variation;
767    }
768
769    /**
770     * Parse a String in the Flex cache language and construct 
771     * the key data structure from this.<p>
772     *
773     * @param key the String to parse (usually read from the file property "cache")
774     */
775    private void parseFlexKey(String key) {
776
777        List<String> tokens = CmsStringUtil.splitAsList(key, ';', false);
778        Iterator<String> i = tokens.iterator();
779        try {
780            while (i.hasNext()) {
781                String t = i.next();
782                String k = null;
783                String v = null;
784                int idx = t.indexOf('=');
785                if (idx >= 0) {
786                    k = t.substring(0, idx).trim();
787                    if (t.length() > idx) {
788                        v = t.substring(idx + 1).trim();
789                    }
790                } else {
791                    k = t.trim();
792                }
793                m_always = 0;
794                if (LOG.isDebugEnabled()) {
795                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_PARSE_FLEXKEY_3, t, k, v));
796                }
797                switch (CACHE_COMMANDS.indexOf(k)) {
798                    case 0: // always
799                    case 12: // true
800                        m_always = 1;
801                        // continue processing (make sure we find a "never" behind "always")
802                        break;
803                    case 1: // never
804                    case 10: // false
805                        m_always = -1;
806                        // no need for any further processing
807                        return;
808                    case 2: // uri
809                        m_uri = IS_USED; // marks m_uri as being used
810                        break;
811                    case 3: // user
812                        m_user = IS_USED; // marks m_user as being used
813                        break;
814                    case 4: // params
815                        if (v != null) {
816                            m_params = parseValueList(v);
817                        } else {
818                            m_params = Collections.emptySet();
819                        }
820
821                        if (m_params.contains(I_CmsResourceLoader.PARAMETER_ELEMENT)) {
822                            // workaround for element setting by parameter in OpenCms < 6.0
823                            m_element = IS_USED;
824                            m_params.remove(I_CmsResourceLoader.PARAMETER_ELEMENT);
825                            if (m_params.size() == 0) {
826                                m_params = null;
827                            }
828                        }
829                        break;
830                    case 5: // no-params
831                        if (v != null) {
832                            // no-params are present
833                            m_noparams = parseValueList(v);
834                        } else {
835                            // never cache with parameters
836                            m_noparams = Collections.emptySet();
837                        }
838                        break;
839                    case 6: // timeout
840                        m_timeout = Integer.parseInt(v);
841                        break;
842                    case 7: // session
843                        m_session = parseValueList(v);
844                        if (m_session.size() <= 0) {
845                            // session must have at last one variable set
846                            m_parseError = true;
847                        }
848                        break;
849                    case 8: // schemes
850                        m_schemes = parseValueList(v);
851                        break;
852                    case 9: // ports
853                        Set<String> ports = parseValueList(v);
854                        m_ports = new HashSet<Integer>(ports.size());
855                        for (String p : ports) {
856                            try {
857                                m_ports.add(Integer.valueOf(p));
858                            } catch (NumberFormatException e) {
859                                // ignore this number
860                            }
861                        }
862                        break;
863                    case 11: // previous parse error - ignore
864                        break;
865                    case 13: // ip
866                        m_ip = IS_USED; // marks ip as being used
867                        break;
868                    case 14: // element
869                        m_element = IS_USED;
870                        break;
871                    case 15: // locale
872                        m_locale = IS_USED;
873                        break;
874                    case 16: // encoding
875                        m_encoding = IS_USED;
876                        break;
877                    case 17: // site
878                        m_site = IS_USED;
879                        break;
880                    case 18: // attrs
881                        if (v != null) {
882                            m_attrs = parseValueList(v);
883                        } else {
884                            m_attrs = null;
885                        }
886                        break;
887                    case 19: // no-attrs
888                        if (v != null) {
889                            // no-attrs are present
890                            m_noattrs = parseValueList(v);
891                        } else {
892                            // never cache with attributes
893                            m_noattrs = Collections.emptySet();
894                        }
895                        break;
896                    case 20: // device
897                        m_device = IS_USED; // marks m_device as being used
898                        break;
899                    case 21: // container element
900                        m_containerElement = IS_USED;
901                        break;
902                    default: // unknown directive, throw error
903                        m_parseError = true;
904                }
905            }
906        } catch (Exception e) {
907            // any Exception here indicates a parsing error
908            if (LOG.isErrorEnabled()) {
909                LOG.error(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_PARSE_ERROR_1, e.toString()), e);
910            }
911            m_parseError = true;
912        }
913        if (m_parseError) {
914            // If string is invalid set cache to "never"
915            m_always = -1;
916        }
917    }
918
919    /** 
920     * A helper method for the parsing process which parses
921     * Strings like groups=(a, b, c).<p>
922     *
923     * @param value the String to parse 
924     * @return a Map that contains of the parsed values, only the keyset of the Map is needed later
925     */
926    private Set<String> parseValueList(String value) {
927
928        if (value.charAt(0) == '(') {
929            value = value.substring(1);
930        }
931        int len = value.length() - 1;
932        if (value.charAt(len) == ')') {
933            value = value.substring(0, len);
934        }
935        if (value.charAt(len - 1) == ',') {
936            value = value.substring(0, len - 1);
937        }
938        if (LOG.isDebugEnabled()) {
939            LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_PARSE_VALUES_1, value));
940        }
941        List<String> tokens = CmsStringUtil.splitAsList(value, ',', true);
942        Set<String> result = new HashSet<String>();
943        result.addAll(tokens);
944        return result;
945    }
946}