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.Optional;
010import java.util.Set;
011import java.util.concurrent.ConcurrentHashMap;
012
013import org.apache.commons.jcs3.engine.ElementAttributes;
014import org.openstreetmap.josm.tools.Logging;
015
016/**
017 * Class that contains attributes for JCS cache entries. Parameters are used to properly handle HTTP caching,
018 * and metadata structures, that should be stored together with the cache entry
019 *
020 * @author Wiktor Niesiobędzki
021 * @since 8168
022 */
023public class CacheEntryAttributes extends ElementAttributes {
024    private static final long serialVersionUID = 1L; //version
025    private final Map<String, String> attrs = new ConcurrentHashMap<>(RESERVED_KEYS.size());
026    private static final String NO_TILE_AT_ZOOM = "noTileAtZoom";
027    private static final String ETAG = "Etag";
028    private static final String LAST_MODIFICATION = "lastModification";
029    private static final String EXPIRATION_TIME = "expirationTime";
030    private static final String HTTP_RESPONSE_CODE = "httpResponseCode";
031    private static final String ERROR_MESSAGE = "errorMessage";
032    private static final String EXCEPTION = "exception";
033    // this contains all of the above
034    private static final Set<String> RESERVED_KEYS = new HashSet<>(Arrays.asList(
035        NO_TILE_AT_ZOOM,
036        ETAG,
037        LAST_MODIFICATION,
038        EXPIRATION_TIME,
039        HTTP_RESPONSE_CODE,
040        ERROR_MESSAGE,
041        EXCEPTION
042    ));
043
044    /**
045     * Constructs a new {@code CacheEntryAttributes}.
046     */
047    public CacheEntryAttributes() {
048        super();
049        attrs.put(NO_TILE_AT_ZOOM, "false");
050        attrs.put(LAST_MODIFICATION, "0");
051        attrs.put(EXPIRATION_TIME, "0");
052        attrs.put(HTTP_RESPONSE_CODE, "200");
053    }
054
055    /**
056     * Determines if the entry is marked as "no tile at this zoom level".
057     * @return if the entry is marked as "no tile at this zoom level"
058     */
059    public boolean isNoTileAtZoom() {
060        return Boolean.toString(true).equals(attrs.get(NO_TILE_AT_ZOOM));
061    }
062
063    /**
064     * Sets the marker for "no tile at this zoom level"
065     * @param noTileAtZoom true if this entry is "no tile at this zoom level"
066     */
067    public void setNoTileAtZoom(boolean noTileAtZoom) {
068        attrs.put(NO_TILE_AT_ZOOM, Boolean.toString(noTileAtZoom));
069    }
070
071    /**
072     * Returns ETag header value, that was returned for this entry.
073     * @return ETag header value, that was returned for this entry.
074     */
075    public String getEtag() {
076        return attrs.get(ETAG);
077    }
078
079    /**
080     * Sets the ETag header that was set with this entry
081     * @param etag Etag header
082     */
083    public void setEtag(String etag) {
084        if (etag != null) {
085            attrs.put(ETAG, etag);
086        }
087    }
088
089    /**
090     * Utility for conversion from String to int, with default to 0, in case of any errors
091     *
092     * @param key - integer as string
093     * @return int value of the string
094     */
095    private long getLongAttr(String key) {
096        try {
097            return Long.parseLong(attrs.computeIfAbsent(key, k -> "0"));
098        } catch (NumberFormatException e) {
099            attrs.put(key, "0");
100            return 0;
101        }
102    }
103
104    /**
105     * Returns last modification of the object in cache in milliseconds from Epoch.
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     * Returns when the object expires in milliseconds from Epoch.
123     * @return when the object expires in milliseconds from Epoch
124     */
125    public long getExpirationTime() {
126        return getLongAttr(EXPIRATION_TIME);
127    }
128
129    /**
130     * sets expiration time for the object in cache
131     *
132     * @param expirationTime in format of milliseconds from epoch
133     */
134    public void setExpirationTime(long expirationTime) {
135        attrs.put(EXPIRATION_TIME, Long.toString(expirationTime));
136    }
137
138    /**
139     * Sets the HTTP response code that was sent with the cache entry
140     *
141     * @param responseCode http status code
142     * @since 8389
143     */
144    public void setResponseCode(int responseCode) {
145        attrs.put(HTTP_RESPONSE_CODE, Integer.toString(responseCode));
146    }
147
148    /**
149     * Returns HTTP response code.
150     * @return http status code
151     * @since 8389
152     */
153    public int getResponseCode() {
154        return (int) getLongAttr(HTTP_RESPONSE_CODE);
155    }
156
157    /**
158     * Sets the metadata about cache entry. As it stores all data together, with other attributes
159     * in common map, some keys might not be stored.
160     *
161     * @param map metadata to save
162     * @since 8418
163     */
164    public void setMetadata(Map<String, String> map) {
165        for (Entry<String, String> e: map.entrySet()) {
166            if (RESERVED_KEYS.contains(e.getKey())) {
167                Logging.info("Metadata key configuration contains key {0} which is reserved for internal use");
168            } else {
169                attrs.put(e.getKey(), e.getValue());
170            }
171        }
172    }
173
174    /**
175     * Returns an unmodifiable Map containing all metadata. Unmodifiable prevents access to metadata within attributes.
176     *
177     * @return unmodifiable Map with cache element metadata
178     * @since 8418
179     */
180    public Map<String, String> getMetadata() {
181        return Collections.unmodifiableMap(attrs);
182    }
183
184    /**
185     * Returns error message returned while retrieving this object.
186     * @return error message returned while retrieving this object
187     */
188    public String getErrorMessage() {
189        return attrs.get(ERROR_MESSAGE);
190    }
191
192    /**
193     * Sets error related to this object.
194     * @param error error related to this object
195     * @since 10469
196     */
197    public void setError(Exception error) {
198        setErrorMessage(Logging.getErrorMessage(error));
199    }
200
201    /**
202     * Sets error message related to this object.
203     * @param message error message related to this object
204     */
205    public void setErrorMessage(String message) {
206        attrs.put(ERROR_MESSAGE, message);
207    }
208
209    /**
210     * Sets exception that caused error.
211     * @param e exception that caused error
212     */
213    public void setException(Exception e) {
214        attrs.put(EXCEPTION, e.getClass().getCanonicalName());
215    }
216
217    /**
218     * Returns optional exception that was thrown when fetching resource.
219     * @return Optional exception that was thrown when fetching resource
220     */
221    public Optional<Class<? extends Exception>> getException() {
222        String className = attrs.get(EXCEPTION);
223        if (className == null) {
224            return Optional.empty();
225        }
226        try {
227            Class<?> klass = Class.forName(className);
228            if (Exception.class.isAssignableFrom(klass)) {
229                return Optional.of(klass.asSubclass(Exception.class));
230            }
231        } catch (ClassNotFoundException | ClassCastException ex) {
232            Logging.trace(ex);
233        }
234        return Optional.empty();
235    }
236}