source: josm/trunk/src/org/openstreetmap/josm/io/CacheCustomContent.java@ 14273

Last change on this file since 14273 was 13647, checked in by Don-vip, 6 years ago

see #16204 - Allow to start and close JOSM in WebStart sandbox mode (where every external access is denied). This was very useful to reproduce some very tricky bugs that occured in real life but were almost impossible to diagnose.

  • Property svn:eol-style set to native
File size: 7.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import java.io.BufferedInputStream;
5import java.io.BufferedOutputStream;
6import java.io.File;
7import java.io.IOException;
8import java.nio.charset.StandardCharsets;
9import java.nio.file.Files;
10import java.nio.file.InvalidPathException;
11import java.util.concurrent.TimeUnit;
12
13import org.openstreetmap.josm.spi.preferences.Config;
14import org.openstreetmap.josm.tools.Logging;
15import org.openstreetmap.josm.tools.Utils;
16
17/**
18 * Use this class if you want to cache and store a single file that gets updated regularly.
19 * Unless you flush() it will be kept in memory. If you want to cache a lot of data and/or files, use CacheFiles.
20 * @author xeen
21 * @param <T> a {@link Throwable} that may be thrown during {@link #updateData()},
22 * use {@link RuntimeException} if no exception must be handled.
23 * @since 1450
24 */
25public abstract class CacheCustomContent<T extends Throwable> {
26
27 /** Update interval meaning an update is always needed */
28 public static final int INTERVAL_ALWAYS = -1;
29 /** Update interval meaning an update is needed each hour */
30 public static final int INTERVAL_HOURLY = (int) TimeUnit.HOURS.toSeconds(1);
31 /** Update interval meaning an update is needed each day */
32 public static final int INTERVAL_DAILY = (int) TimeUnit.DAYS.toSeconds(1);
33 /** Update interval meaning an update is needed each week */
34 public static final int INTERVAL_WEEKLY = (int) TimeUnit.DAYS.toSeconds(7);
35 /** Update interval meaning an update is needed each month */
36 public static final int INTERVAL_MONTHLY = (int) TimeUnit.DAYS.toSeconds(28);
37 /** Update interval meaning an update is never needed */
38 public static final int INTERVAL_NEVER = Integer.MAX_VALUE;
39
40 /**
41 * Where the data will be stored
42 */
43 private byte[] data;
44
45 /**
46 * The ident that identifies the stored file. Includes file-ending.
47 */
48 private final String ident;
49
50 /**
51 * The (file-)path where the data will be stored
52 */
53 private final File path;
54
55 /**
56 * How often to update the cached version
57 */
58 private final int updateInterval;
59
60 /**
61 * This function will be executed when an update is required. It has to be implemented by the
62 * inheriting class and should use a worker if it has a long wall time as the function is
63 * executed in the current thread.
64 * @return the data to cache
65 * @throws T a {@link Throwable}
66 */
67 protected abstract byte[] updateData() throws T;
68
69 /**
70 * Initializes the class. Note that all read data will be stored in memory until it is flushed
71 * by flushData().
72 * @param ident ident that identifies the stored file. Includes file-ending.
73 * @param updateInterval update interval in seconds. -1 means always
74 */
75 public CacheCustomContent(String ident, int updateInterval) {
76 this.ident = ident;
77 this.updateInterval = updateInterval;
78 this.path = new File(Config.getDirs().getCacheDirectory(true), ident);
79 }
80
81 /**
82 * This function serves as a comfort hook to perform additional checks if the cache is valid
83 * @return True if the cached copy is still valid
84 */
85 protected boolean isCacheValid() {
86 return true;
87 }
88
89 private boolean needsUpdate() {
90 if (isOffline()) {
91 return false;
92 }
93 return Config.getPref().getInt("cache." + ident, 0) + updateInterval < TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
94 || !isCacheValid();
95 }
96
97 private boolean isOffline() {
98 try {
99 checkOfflineAccess();
100 return false;
101 } catch (OfflineAccessException e) {
102 Logging.trace(e);
103 return true;
104 }
105 }
106
107 /**
108 * Ensures underlying resource is not accessed in offline mode.
109 * @throws OfflineAccessException if resource is accessed in offline mode
110 */
111 protected abstract void checkOfflineAccess();
112
113 /**
114 * Updates data if required
115 * @return Returns the data
116 * @throws T if an error occurs
117 */
118 public byte[] updateIfRequired() throws T {
119 if (needsUpdate())
120 return updateForce();
121 return getData();
122 }
123
124 /**
125 * Updates data if required
126 * @return Returns the data as string
127 * @throws T if an error occurs
128 */
129 public String updateIfRequiredString() throws T {
130 if (needsUpdate())
131 return updateForceString();
132 return getDataString();
133 }
134
135 /**
136 * Executes an update regardless of updateInterval
137 * @return Returns the data
138 * @throws T if an error occurs
139 */
140 private byte[] updateForce() throws T {
141 this.data = updateData();
142 saveToDisk();
143 Config.getPref().putInt("cache." + ident, (int) (TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
144 return data;
145 }
146
147 /**
148 * Executes an update regardless of updateInterval
149 * @return Returns the data as String
150 * @throws T if an error occurs
151 */
152 public String updateForceString() throws T {
153 updateForce();
154 return new String(data, StandardCharsets.UTF_8);
155 }
156
157 /**
158 * Returns the data without performing any updates
159 * @return the data
160 * @throws T if an error occurs
161 */
162 public byte[] getData() throws T {
163 if (data == null) {
164 loadFromDisk();
165 }
166 return Utils.copyArray(data);
167 }
168
169 /**
170 * Returns the data without performing any updates
171 * @return the data as String
172 * @throws T if an error occurs
173 */
174 public String getDataString() throws T {
175 byte[] array = getData();
176 if (array == null) {
177 return null;
178 }
179 return new String(array, StandardCharsets.UTF_8);
180 }
181
182 /**
183 * Tries to load the data using the given ident from disk. If this fails, data will be updated, unless run in offline mode
184 * @throws T a {@link Throwable}
185 */
186 private void loadFromDisk() throws T {
187 try (BufferedInputStream input = new BufferedInputStream(Files.newInputStream(path.toPath()))) {
188 this.data = new byte[input.available()];
189 if (input.read(this.data) < this.data.length) {
190 Logging.error("Failed to read expected contents from "+path);
191 }
192 } catch (IOException | InvalidPathException e) {
193 Logging.trace(e);
194 if (!isOffline()) {
195 this.data = updateForce();
196 }
197 }
198 }
199
200 /**
201 * Stores the data to disk
202 */
203 private void saveToDisk() {
204 try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(path.toPath()))) {
205 output.write(this.data);
206 output.flush();
207 } catch (IOException | InvalidPathException | SecurityException e) {
208 Logging.log(Logging.LEVEL_ERROR, "Unable to save data", e);
209 }
210 }
211
212 /**
213 * Flushes the data from memory. Class automatically reloads it from disk or updateData() if required
214 */
215 public void flushData() {
216 data = null;
217 }
218}
Note: See TracBrowser for help on using the repository browser.