1 package com.trendmicro.grid.acl.ds.cifs;
2
3 import com.trendmicro.grid.acl.ds.FileContentRepository;
4 import net.sf.tinyjee.config.PropertySection;
5 import net.sf.tinyjee.util.Hex;
6 import org.slf4j.Logger;
7 import org.slf4j.LoggerFactory;
8
9 import javax.annotation.PostConstruct;
10 import java.io.FileNotFoundException;
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.io.OutputStream;
14 import java.net.URI;
15 import java.nio.ByteBuffer;
16 import java.security.DigestOutputStream;
17 import java.security.MessageDigest;
18 import java.security.NoSuchAlgorithmException;
19 import java.util.*;
20 import java.util.concurrent.atomic.AtomicLong;
21
22 import static com.trendmicro.grid.acl.ds.cifs.CIFSPropertySection.*;
23 import static net.sf.tinyjee.util.Assert.assertEquals;
24 import static net.sf.tinyjee.util.Assert.assertNotNull;
25
26
27
28
29
30
31
32 public abstract class AbstractStructuredFileRepository implements FileContentRepository {
33
34 private static final Logger log = LoggerFactory.getLogger(AbstractStructuredFileRepository.class);
35
36
37 private MessageDigest templateHashDigest;
38 private int temporaryHashLength, hashLength;
39
40 private final Random temporaryHashRandom = new Random();
41 private final long temporaryHashSeed = System.currentTimeMillis();
42 private final AtomicLong temporaryHashIndex = new AtomicLong();
43
44 private final String pathSeparator = "/";
45
46 int[] pathScheme;
47 String fileExtension = "";
48 private String baseURIPrefix = "";
49 private String temporaryPathPrefix = "temporary";
50
51
52 protected abstract boolean isExisting(String path);
53
54 protected abstract Date getLastModified(String path);
55
56 protected abstract OutputStream openForWrite(String path)
57 throws FileNotFoundException, IllegalStateException;
58
59 protected abstract InputStream openForRead(String path)
60 throws FileNotFoundException;
61
62 protected abstract void rename(String fromPath, String toPath)
63 throws FileNotFoundException, IllegalArgumentException;
64
65 protected abstract String getBaseURI(String uriPrefix);
66
67
68
69
70
71
72 @PostConstruct
73 public void initialize() throws NoSuchAlgorithmException {
74 PropertySection section = CIFSPropertySection.getPropertySection();
75 initialize(section);
76 }
77
78
79
80
81
82
83
84 protected void initialize(PropertySection section) throws NoSuchAlgorithmException {
85 String algorithm = section.getPropertyValue(KEY_FILENAME_HASH_KEY_ALGORITHM, "SHA1");
86 templateHashDigest = MessageDigest.getInstance(algorithm);
87 hashLength = templateHashDigest.getDigestLength();
88 temporaryHashLength = hashLength + 2;
89 log.info("TMACL-00880:Using {} as filename hash key algorithm. " +
90 "Resulting file paths will have a depth of {} levels.",
91 templateHashDigest.getAlgorithm(), hashLength / 2);
92
93
94 baseURIPrefix = section.getPropertyValue(KEY_INTERNAL_URI_PREFIX, baseURIPrefix);
95 if (baseURIPrefix.isEmpty()) {
96 log.warn("TMACL-00850:No internal URI prefix is defined to address the file repository. " +
97 "Services that depend on a valid internal URIs may not be able access stored content.");
98 } else {
99 log.info("TMACL-00870:Internal URIs pointing to the file " +
100 "repository are prefixed/start with {}", baseURIPrefix);
101 }
102
103 fileExtension = section.getPropertyValue(KEY_FILENAME_EXTENSION, fileExtension);
104 if (fileExtension.isEmpty()) {
105 log.info("TMACL-00890:No filename extension is configured, directories and files may " +
106 "overlap if the hashing scheme is changed at a later stage.");
107 } else {
108 log.info("TMACL-00900:Files are identified inside the file repository " +
109 "using the extension '{}'", fileExtension);
110 }
111
112 createPathScheme(section);
113 }
114
115 void createPathScheme(PropertySection section) {
116 String pathSchemePattern = section.getPropertyValue(KEY_FILENAME_PATH_SCHEMA, "");
117 if (pathSchemePattern.isEmpty()) {
118 pathScheme = new int[64];
119 Arrays.fill(pathScheme, 2);
120 } else {
121 List<Integer> schemeBuffer = new ArrayList<Integer>();
122 for (String s : pathSchemePattern.split("/")) {
123 int increment = (int) Math.ceil(s.length() / 2D);
124 if (increment > 0)
125 schemeBuffer.add(increment);
126 }
127 pathScheme = new int[schemeBuffer.size()];
128 for (int i = 0; i < schemeBuffer.size(); i++)
129 pathScheme[i] = schemeBuffer.get(i);
130
131 log.info("TMACL-01500:Using a customized filename path scheme from '{}' is mapped as '{}'",
132 pathSchemePattern, schemeBuffer);
133 }
134 }
135
136
137
138
139
140
141
142
143 protected final String convertToPath(byte[] hash, boolean isProperties) {
144 assertNotNull("hash", hash);
145 final StringBuilder path = new StringBuilder((hash.length * 2) + 16);
146 try {
147 if (hash.length == hashLength) {
148
149 int j = 0, i, partLength;
150 for (i = 0; i < hash.length && j < pathScheme.length;) {
151 if (i != 0)
152 path.append(pathSeparator);
153 partLength = pathScheme[j++];
154 Hex.encode(path, hash, i, Math.min(partLength, hash.length - i));
155 i += partLength;
156 }
157
158
159 if (i < hash.length) {
160 if (path.length() != 0)
161 path.append(pathSeparator);
162 Hex.encode(path, hash, i, hash.length - i);
163 }
164
165 } else if (hash.length == temporaryHashLength) {
166 Hex.encode(path.append(temporaryPathPrefix).append(pathSeparator), hash);
167 } else
168 throw new IllegalArgumentException("The specified hash doesn't seem to refer to a valid resource.");
169
170 if (isProperties)
171 path.append(".info");
172 else if (!fileExtension.isEmpty())
173 path.append(fileExtension);
174
175 return path.toString();
176
177 } catch (IOException e) {
178 throw new RuntimeException(e);
179 }
180 }
181
182 protected final byte[] convertFromPath(String path) {
183 if (path.endsWith(".info"))
184 path = path.substring(0, path.length() - 5);
185 if (!fileExtension.isEmpty() && path.endsWith(fileExtension))
186 path = path.substring(0, path.length() - fileExtension.length());
187 if (path.startsWith(temporaryPathPrefix))
188 path = path.substring(temporaryPathPrefix.length());
189
190 final StringBuilder hash = new StringBuilder(40);
191 final StringTokenizer tok = new StringTokenizer(path, "\\/");
192 while (tok.hasMoreTokens())
193 hash.append(tok.nextToken());
194
195 return Hex.decode(hash);
196 }
197
198 private void assertIsValidHashKey(byte[] hashKey) {
199 assertNotNull("hashKey", hashKey);
200 if (hashKey.length != hashLength && hashKey.length != temporaryHashLength) {
201 throw new IllegalArgumentException("The specified hash key is neither a key that identifies " +
202 "a temporary nor a normal resource.");
203 }
204 }
205
206 public int getTemporaryHashLength() {
207 return temporaryHashLength;
208 }
209
210 public int getHashLength() {
211 return hashLength;
212 }
213
214
215
216
217 public final String getHashKeyAlgorithm() {
218 return templateHashDigest.getAlgorithm();
219 }
220
221
222
223
224 public final MessageDigest newHashKeyDigest() {
225 try {
226 return (MessageDigest) templateHashDigest.clone();
227 } catch (CloneNotSupportedException e) {
228 throw new RuntimeException(e);
229 }
230 }
231
232
233
234
235 public void attachProperties(byte[] hashKey, Map<String, String> properties) throws IOException {
236 if (!isExisting(hashKey)) {
237 throw new FileNotFoundException("Cannot store properties " + properties + " for " +
238 Hex.encode(hashKey) + ", no content exists under this identifier.");
239 }
240
241 String propertiesPath = convertToPath(hashKey, true);
242
243 OutputStream out = openForWrite(propertiesPath);
244 try {
245 Properties p = new Properties();
246 p.putAll(properties);
247 p.store(out, "");
248 } finally {
249 out.close();
250 }
251 }
252
253
254
255
256 @SuppressWarnings("unchecked")
257 public Map<String, String> readProperties(byte[] hashKey) throws IOException {
258 if (!isExisting(hashKey)) {
259 throw new FileNotFoundException("Cannot load properties for " +
260 Hex.encode(hashKey) + ", no content exists under this identifier.");
261 }
262
263 String propertiesPath = convertToPath(hashKey, true);
264
265 try {
266 InputStream in = openForRead(propertiesPath);
267 try {
268 Properties p = new Properties();
269 p.load(in);
270 return (Map) p;
271 } finally {
272 in.close();
273 }
274 } catch (FileNotFoundException e) {
275 return null;
276 }
277 }
278
279
280
281
282 public final boolean isExisting(byte[] hashKey) {
283 String path = convertToPath(hashKey, false);
284 return isExisting(path);
285 }
286
287
288
289
290 public final Date getLastModified(byte[] hashKey) {
291 String path = convertToPath(hashKey, false);
292 return getLastModified(path);
293 }
294
295
296
297
298 public final DigestOutputStream openForWrite(byte[] hashKey) throws FileNotFoundException, IllegalStateException {
299 String path = convertToPath(hashKey, false);
300 return new DigestOutputStream(openForWrite(path), newHashKeyDigest());
301 }
302
303
304
305
306 public final InputStream openForRead(byte[] hashKey) throws FileNotFoundException {
307 String path = convertToPath(hashKey, false);
308 return openForRead(path);
309 }
310
311
312
313
314
315
316 public final void rename(byte[] fromHashKey, byte[] toHashKey)
317 throws FileNotFoundException, IllegalArgumentException {
318
319
320 assertNotNull("fromHashKey", fromHashKey);
321 assertNotNull("toHashKey", toHashKey);
322 assertEquals("fromHashKey#length", temporaryHashLength, fromHashKey.length);
323 assertEquals("toHashKey#length", hashLength, toHashKey.length);
324
325 String fromPath = convertToPath(fromHashKey, false);
326 String toPath = convertToPath(toHashKey, false);
327 rename(fromPath, toPath);
328 }
329
330
331
332
333 public final byte[] createTemporaryHashKey() {
334 ByteBuffer key = ByteBuffer.allocate(temporaryHashLength);
335 temporaryHashRandom.nextBytes(key.array());
336 key.putLong(temporaryHashSeed);
337 key.putLong(temporaryHashIndex.incrementAndGet());
338 return key.array();
339 }
340
341
342
343
344 public URI encodeToInternalURI(byte[] hashKey) {
345 assertIsValidHashKey(hashKey);
346 return URI.create(getBaseURI(baseURIPrefix) + convertToPath(hashKey, false));
347 }
348
349
350
351
352 public byte[] decodeFromInternalURI(URI internalURI) {
353 String baseURI = getBaseURI(baseURIPrefix), uri = internalURI.toString();
354 if (!uri.startsWith(baseURI))
355 return null;
356 return convertFromPath(uri.substring(baseURI.length()));
357 }
358
359
360
361
362 public final String encodeHashKey(byte[] hashKey) {
363 assertIsValidHashKey(hashKey);
364 return Hex.encode(hashKey);
365 }
366
367
368
369
370 public final byte[] decodeHashKey(String hashKey) {
371 byte[] hash = Hex.decode(hashKey);
372 assertIsValidHashKey(hash);
373 return hash;
374 }
375 }