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}