001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.cache;
003
004import java.util.Arrays;
005import java.util.Collections;
006import java.util.HashSet;
007import java.util.Map;
008import java.util.Map.Entry;
009import java.util.Set;
010import java.util.concurrent.ConcurrentHashMap;
011
012import org.apache.commons.jcs.engine.ElementAttributes;
013import org.openstreetmap.josm.Main;
014
015/**
016 * Class that contains attributes for JCS cache entries. Parameters are used to properly handle HTTP caching,
017 * and metadata structures, that should be stored together with the cache entry
018 *
019 * @author Wiktor Niesiobędzki
020 * @since 8168
021 */
022public class CacheEntryAttributes extends ElementAttributes {
023    private static final long serialVersionUID = 1L; //version
024    private final Map<String, String> attrs = new ConcurrentHashMap<String, String>(RESERVED_KEYS.size());
025    private static final String NO_TILE_AT_ZOOM = "noTileAtZoom";
026    private static final String ETAG = "Etag";
027    private static final String LAST_MODIFICATION = "lastModification";
028    private static final String EXPIRATION_TIME = "expirationTime";
029    private static final String HTTP_RESPONSE_CODE = "httpResponceCode";
030    private static final String ERROR_MESSAGE = "errorMessage";
031    // this contains all of the above
032    private static final Set<String> RESERVED_KEYS = new HashSet<>(Arrays.asList(new String[]{
033        NO_TILE_AT_ZOOM,
034        ETAG,
035        LAST_MODIFICATION,
036        EXPIRATION_TIME,
037        HTTP_RESPONSE_CODE,
038        ERROR_MESSAGE
039    }));
040
041
042    /**
043     * Constructs a new {@code CacheEntryAttributes}.
044     */
045    public CacheEntryAttributes() {
046        super();
047        attrs.put(NO_TILE_AT_ZOOM, "false");
048        attrs.put(LAST_MODIFICATION, "0");
049        attrs.put(EXPIRATION_TIME, "0");
050        attrs.put(HTTP_RESPONSE_CODE, "200");
051    }
052
053    /**
054     * @return if the entry is marked as "no tile at this zoom level"
055     */
056    public boolean isNoTileAtZoom() {
057        return Boolean.toString(true).equals(attrs.get(NO_TILE_AT_ZOOM));
058    }
059
060    /**
061     * Sets the marker for "no tile at this zoom level"
062     * @param noTileAtZoom true if this entry is "no tile at this zoom level"
063     */
064    public void setNoTileAtZoom(boolean noTileAtZoom) {
065        attrs.put(NO_TILE_AT_ZOOM, Boolean.toString(noTileAtZoom));
066    }
067
068    /**
069     * @return ETag header value, that was returned for this entry.
070     */
071    public String getEtag() {
072        return attrs.get(ETAG);
073    }
074
075    /**
076     * Sets the ETag header that was set with this entry
077     * @param etag Etag header
078     */
079    public void setEtag(String etag) {
080        if (etag != null) {
081            attrs.put(ETAG, etag);
082        }
083    }
084
085    /**
086     * Utility for conversion from String to int, with default to 0, in case of any errors
087     *
088     * @param key - integer as string
089     * @return int value of the string
090     */
091    private long getLongAttr(String key) {
092        String val = attrs.get(key);
093        if (val == null) {
094            attrs.put(key,  "0");
095            return 0;
096        }
097        try {
098            return Long.parseLong(val);
099        } catch (NumberFormatException e) {
100            attrs.put(key, "0");
101            return 0;
102        }
103    }
104
105    /**
106     * @return last modification of the object in cache in milliseconds from Epoch
107     */
108    public long getLastModification() {
109        return getLongAttr(LAST_MODIFICATION);
110    }
111
112    /**
113     * sets last modification of the object in cache
114     *
115     * @param lastModification time in format of milliseconds from Epoch
116     */
117    public void setLastModification(long lastModification) {
118        attrs.put(LAST_MODIFICATION, Long.toString(lastModification));
119    }
120
121    /**
122     * @return when the object expires in milliseconds from Epoch
123     */
124    public long getExpirationTime() {
125        return getLongAttr(EXPIRATION_TIME);
126    }
127
128    /**
129     * sets expiration time for the object in cache
130     *
131     * @param expirationTime in format of milliseconds from epoch
132     */
133    public void setExpirationTime(long expirationTime) {
134        attrs.put(EXPIRATION_TIME, Long.toString(expirationTime));
135    }
136
137    /**
138     * Sets the HTTP response code that was sent with the cache entry
139     *
140     * @param responseCode http status code
141     * @since 8389
142     */
143    public void setResponseCode(int responseCode) {
144        attrs.put(HTTP_RESPONSE_CODE, Integer.toString(responseCode));
145    }
146
147    /**
148     * @return http status code
149     * @since 8389
150     */
151    public int getResponseCode() {
152        return (int) getLongAttr(HTTP_RESPONSE_CODE);
153    }
154
155    /**
156     * Sets the metadata about cache entry. As it stores all data together, with other attributes
157     * in common map, some keys might not be stored.
158     *
159     * @param map metadata to save
160     * @since 8418
161     */
162    public void setMetadata(Map<String, String> map) {
163        for (Entry<String, String> e: map.entrySet()) {
164            if (RESERVED_KEYS.contains(e.getKey())) {
165                Main.info("Metadata key configuration contains key {0} which is reserved for internal use");
166            } else {
167                attrs.put(e.getKey(), e.getValue());
168            }
169        }
170    }
171
172    /**
173     * Returns an unmodifiable Map containing all metadata. Unmodifiable prevents access to metadata within attributes.
174     *
175     * @return unmodifiable Map with cache element metadata
176     * @since 8418
177     */
178    public Map<String, String> getMetadata() {
179        return Collections.unmodifiableMap(attrs);
180    }
181
182    /**
183     * @return error message returned while retrieving this object
184     */
185    public String getErrorMessage() {
186        return attrs.get(ERROR_MESSAGE);
187    }
188
189    /**
190     * @param message error message related to this object
191     */
192    public void setErrorMessage(String message) {
193        attrs.put(ERROR_MESSAGE, message);
194    }
195}