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