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.tools.Logging;
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<>(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 = "httpResponseCode";
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(
033        NO_TILE_AT_ZOOM,
034        ETAG,
035        LAST_MODIFICATION,
036        EXPIRATION_TIME,
037        HTTP_RESPONSE_CODE,
038        ERROR_MESSAGE
039    ));
040
041    /**
042     * Constructs a new {@code CacheEntryAttributes}.
043     */
044    public CacheEntryAttributes() {
045        super();
046        attrs.put(NO_TILE_AT_ZOOM, "false");
047        attrs.put(LAST_MODIFICATION, "0");
048        attrs.put(EXPIRATION_TIME, "0");
049        attrs.put(HTTP_RESPONSE_CODE, "200");
050    }
051
052    /**
053     * @return if the entry is marked as "no tile at this zoom level"
054     */
055    public boolean isNoTileAtZoom() {
056        return Boolean.toString(true).equals(attrs.get(NO_TILE_AT_ZOOM));
057    }
058
059    /**
060     * Sets the marker for "no tile at this zoom level"
061     * @param noTileAtZoom true if this entry is "no tile at this zoom level"
062     */
063    public void setNoTileAtZoom(boolean noTileAtZoom) {
064        attrs.put(NO_TILE_AT_ZOOM, Boolean.toString(noTileAtZoom));
065    }
066
067    /**
068     * @return ETag header value, that was returned for this entry.
069     */
070    public String getEtag() {
071        return attrs.get(ETAG);
072    }
073
074    /**
075     * Sets the ETag header that was set with this entry
076     * @param etag Etag header
077     */
078    public void setEtag(String etag) {
079        if (etag != null) {
080            attrs.put(ETAG, etag);
081        }
082    }
083
084    /**
085     * Utility for conversion from String to int, with default to 0, in case of any errors
086     *
087     * @param key - integer as string
088     * @return int value of the string
089     */
090    private long getLongAttr(String key) {
091        try {
092            return Long.parseLong(attrs.computeIfAbsent(key, k -> "0"));
093        } catch (NumberFormatException e) {
094            attrs.put(key, "0");
095            return 0;
096        }
097    }
098
099    /**
100     * @return last modification of the object in cache in milliseconds from Epoch
101     */
102    public long getLastModification() {
103        return getLongAttr(LAST_MODIFICATION);
104    }
105
106    /**
107     * sets last modification of the object in cache
108     *
109     * @param lastModification time in format of milliseconds from Epoch
110     */
111    public void setLastModification(long lastModification) {
112        attrs.put(LAST_MODIFICATION, Long.toString(lastModification));
113    }
114
115    /**
116     * @return when the object expires in milliseconds from Epoch
117     */
118    public long getExpirationTime() {
119        return getLongAttr(EXPIRATION_TIME);
120    }
121
122    /**
123     * sets expiration time for the object in cache
124     *
125     * @param expirationTime in format of milliseconds from epoch
126     */
127    public void setExpirationTime(long expirationTime) {
128        attrs.put(EXPIRATION_TIME, Long.toString(expirationTime));
129    }
130
131    /**
132     * Sets the HTTP response code that was sent with the cache entry
133     *
134     * @param responseCode http status code
135     * @since 8389
136     */
137    public void setResponseCode(int responseCode) {
138        attrs.put(HTTP_RESPONSE_CODE, Integer.toString(responseCode));
139    }
140
141    /**
142     * @return http status code
143     * @since 8389
144     */
145    public int getResponseCode() {
146        return (int) getLongAttr(HTTP_RESPONSE_CODE);
147    }
148
149    /**
150     * Sets the metadata about cache entry. As it stores all data together, with other attributes
151     * in common map, some keys might not be stored.
152     *
153     * @param map metadata to save
154     * @since 8418
155     */
156    public void setMetadata(Map<String, String> map) {
157        for (Entry<String, String> e: map.entrySet()) {
158            if (RESERVED_KEYS.contains(e.getKey())) {
159                Logging.info("Metadata key configuration contains key {0} which is reserved for internal use");
160            } else {
161                attrs.put(e.getKey(), e.getValue());
162            }
163        }
164    }
165
166    /**
167     * Returns an unmodifiable Map containing all metadata. Unmodifiable prevents access to metadata within attributes.
168     *
169     * @return unmodifiable Map with cache element metadata
170     * @since 8418
171     */
172    public Map<String, String> getMetadata() {
173        return Collections.unmodifiableMap(attrs);
174    }
175
176    /**
177     * @return error message returned while retrieving this object
178     */
179    public String getErrorMessage() {
180        return attrs.get(ERROR_MESSAGE);
181    }
182
183    /**
184     * @param error error related to this object
185     * @since 10469
186     */
187    public void setError(Exception error) {
188        setErrorMessage(Logging.getErrorMessage(error));
189    }
190
191    /**
192     * @param message error message related to this object
193     */
194    public void setErrorMessage(String message) {
195        attrs.put(ERROR_MESSAGE, message);
196    }
197}