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}