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