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.jcs.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 * @return if the entry is marked as "no tile at this zoom level" 057 */ 058 public boolean isNoTileAtZoom() { 059 return Boolean.toString(true).equals(attrs.get(NO_TILE_AT_ZOOM)); 060 } 061 062 /** 063 * Sets the marker for "no tile at this zoom level" 064 * @param noTileAtZoom true if this entry is "no tile at this zoom level" 065 */ 066 public void setNoTileAtZoom(boolean noTileAtZoom) { 067 attrs.put(NO_TILE_AT_ZOOM, Boolean.toString(noTileAtZoom)); 068 } 069 070 /** 071 * @return ETag header value, that was returned for this entry. 072 */ 073 public String getEtag() { 074 return attrs.get(ETAG); 075 } 076 077 /** 078 * Sets the ETag header that was set with this entry 079 * @param etag Etag header 080 */ 081 public void setEtag(String etag) { 082 if (etag != null) { 083 attrs.put(ETAG, etag); 084 } 085 } 086 087 /** 088 * Utility for conversion from String to int, with default to 0, in case of any errors 089 * 090 * @param key - integer as string 091 * @return int value of the string 092 */ 093 private long getLongAttr(String key) { 094 try { 095 return Long.parseLong(attrs.computeIfAbsent(key, k -> "0")); 096 } catch (NumberFormatException e) { 097 attrs.put(key, "0"); 098 return 0; 099 } 100 } 101 102 /** 103 * @return last modification of the object in cache in milliseconds from Epoch 104 */ 105 public long getLastModification() { 106 return getLongAttr(LAST_MODIFICATION); 107 } 108 109 /** 110 * sets last modification of the object in cache 111 * 112 * @param lastModification time in format of milliseconds from Epoch 113 */ 114 public void setLastModification(long lastModification) { 115 attrs.put(LAST_MODIFICATION, Long.toString(lastModification)); 116 } 117 118 /** 119 * @return when the object expires in milliseconds from Epoch 120 */ 121 public long getExpirationTime() { 122 return getLongAttr(EXPIRATION_TIME); 123 } 124 125 /** 126 * sets expiration time for the object in cache 127 * 128 * @param expirationTime in format of milliseconds from epoch 129 */ 130 public void setExpirationTime(long expirationTime) { 131 attrs.put(EXPIRATION_TIME, Long.toString(expirationTime)); 132 } 133 134 /** 135 * Sets the HTTP response code that was sent with the cache entry 136 * 137 * @param responseCode http status code 138 * @since 8389 139 */ 140 public void setResponseCode(int responseCode) { 141 attrs.put(HTTP_RESPONSE_CODE, Integer.toString(responseCode)); 142 } 143 144 /** 145 * @return http status code 146 * @since 8389 147 */ 148 public int getResponseCode() { 149 return (int) getLongAttr(HTTP_RESPONSE_CODE); 150 } 151 152 /** 153 * Sets the metadata about cache entry. As it stores all data together, with other attributes 154 * in common map, some keys might not be stored. 155 * 156 * @param map metadata to save 157 * @since 8418 158 */ 159 public void setMetadata(Map<String, String> map) { 160 for (Entry<String, String> e: map.entrySet()) { 161 if (RESERVED_KEYS.contains(e.getKey())) { 162 Logging.info("Metadata key configuration contains key {0} which is reserved for internal use"); 163 } else { 164 attrs.put(e.getKey(), e.getValue()); 165 } 166 } 167 } 168 169 /** 170 * Returns an unmodifiable Map containing all metadata. Unmodifiable prevents access to metadata within attributes. 171 * 172 * @return unmodifiable Map with cache element metadata 173 * @since 8418 174 */ 175 public Map<String, String> getMetadata() { 176 return Collections.unmodifiableMap(attrs); 177 } 178 179 /** 180 * @return error message returned while retrieving this object 181 */ 182 public String getErrorMessage() { 183 return attrs.get(ERROR_MESSAGE); 184 } 185 186 /** 187 * @param error error related to this object 188 * @since 10469 189 */ 190 public void setError(Exception error) { 191 setErrorMessage(Logging.getErrorMessage(error)); 192 } 193 194 /** 195 * @param message error message related to this object 196 */ 197 public void setErrorMessage(String message) { 198 attrs.put(ERROR_MESSAGE, message); 199 } 200 201 /** 202 * @param e exception that caused error 203 * 204 */ 205 public void setException(Exception e) { 206 attrs.put(EXCEPTION, e.getClass().getCanonicalName()); 207 } 208 209 /** 210 * @return Optional exception that was thrown when fetching resource 211 * 212 */ 213 public Optional<Class<? extends Exception>> getException() { 214 String className = attrs.get(EXCEPTION); 215 if (className == null) { 216 return Optional.empty(); 217 } 218 try { 219 Class<?> klass = Class.forName(className); 220 if (Exception.class.isAssignableFrom(klass)) { 221 return Optional.of(klass.asSubclass(Exception.class)); 222 } 223 } catch (ClassNotFoundException | ClassCastException ex) { 224 Logging.trace(ex); 225 } 226 return Optional.empty(); 227 } 228}