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