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