001 /*
002 * Created by IntelliJ IDEA.
003 * User: d@hd.org
004 * Date: 27-May-02
005 * Time: 09:32:06
006
007 Copyright (c) 1996-2012, Damon Hart-Davis
008 All rights reserved.
009
010 Redistribution and use in source and binary forms, with or without
011 modification, are permitted provided that the following conditions are
012 met:
013
014 * Redistributions of source code must retain the above copyright
015 notice, this list of conditions and the following disclaimer.
016
017 * Redistributions in binary form must reproduce the above copyright
018 notice, this list of conditions and the following disclaimer in the
019 documentation and/or other materials provided with the
020 distribution.
021
022 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
023 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
024 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
025 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
026 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
027 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
028 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
029 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
030 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
031 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
032 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
033
034 */
035 package org.hd.d.pg2k.svrCore.props;
036
037 // We try to depend as little as possible on other classes in the
038 // system as we need to initialise early during bootstrapping.
039 import java.io.BufferedInputStream;
040 import java.io.File;
041 import java.io.FileInputStream;
042 import java.io.IOException;
043 import java.io.InputStream;
044 import java.net.URI;
045 import java.net.URISyntaxException;
046 import java.security.MessageDigest;
047 import java.util.ArrayList;
048 import java.util.Collections;
049 import java.util.HashMap;
050 import java.util.HashSet;
051 import java.util.List;
052 import java.util.Map;
053 import java.util.Properties;
054 import java.util.Set;
055
056 import javax.crypto.SecretKey;
057
058 import org.hd.d.pg2k.svrCore.CoreConsts;
059 import org.hd.d.pg2k.svrCore.FileTools;
060 import org.hd.d.pg2k.svrCore.MemoryTools;
061 import org.hd.d.pg2k.svrCore.location.GeoUtils;
062
063 import ORG.hd.d.IsDebug;
064
065 /**This is a set of properties local to the JVM.
066 * This is the NEAR-minimum set of properties to do with security
067 * or local system configuration (such as the local layout
068 * of the file-system) that must not or cannot be sent over
069 * the network and shared between master and slaves.
070 * Much of this is bootstrapping data, such as the location of various
071 * configuration and persistent-state directories.
072 * Our aim is to minimise the need for other parts of the system
073 * to get at random JVM system properties and files, etc, where possible.
074 * <p>
075 * Some of the more sensitive data are made available
076 * via package-visible members so that the other props
077 * classes can use them but nothing else can see the values
078 * directly.
079 * <p>
080 * These properties are not Serializable and are never sent
081 * across the network (unlike GenProps), and have to be
082 * made available in a local file and/or system properties
083 * for example.
084 * (Do not include local properties file in the WAR or EAR file.)
085 * <p>
086 * These properties can be checked for being up-to-date
087 * from time to time, and the in-memory values will be
088 * refreshed if necessary.
089 * <p>
090 * Values set purely from system properties may be collected
091 * once at class creation and not again.
092 * <p>
093 * All access to values is via static methods
094 * appropriately synchronized to ensure thread-safety.
095 * <p>
096 * This is necessarily a rag-tag of values used all over the system,
097 * and will be loaded very early and should not depend on much of the
098 * rest of the system if at all.
099 */
100 public final class LocalProps
101 {
102 /**Name of cloud-mirror tag system property; non-null and non-empty.
103 * If the named system property is set to a valid mirror tag
104 * then this instance is a light-weight 'micro' cloud instance.
105 * <p>
106 * This is unqualified to help keep command-line parameters short.
107 */
108 public static final String PNAME_MICRO_CLOUD_TAG = "MicroTag";
109
110 /**Validated value of micro tag, or null if not set or not syntactically valid. */
111 private static final String microTag;
112 static
113 {
114 String mt = null;
115 try { mt = validateMirrorTag(System.getProperty(PNAME_MICRO_CLOUD_TAG)); }
116 catch(final SecurityException e) { /* Treat lack of permission as if lack of value. */ }
117 microTag = mt;
118 }
119
120 /**If true then this (WAR) instance is (permanently) configured as a light-weight cloud-hosted mirror.
121 * This may imply multiple behaviour and configuration changes throughout the system.
122 * <p>
123 * Implications of this being true are that CPU, bandwidth and file space may all be metered,
124 * and there may be no file access outside the WAR working cache.
125 * <p>
126 * Fast and non-blocking.
127 * <p>
128 * This state does not change for a running instance.
129 */
130 public static boolean isCloudMirrorInstance()
131 { return(null != microTag); }
132
133 /**True if this instance should make no filesystem access outside the WAR cache. */
134 private static final boolean noGeneralFileAccess = LocalProps.isCloudMirrorInstance();
135
136 /**True if this instance should make no filesystem access outside the WAR cache. */
137 public static boolean getNoGeneralFileAccess() { return(noGeneralFileAccess); }
138
139 /**Veto attempted actual or implied file access with an exception if not permitted.
140 * @throws UnsupportedOperationException to veto such attempted access
141 */
142 private static void vetoFileAccessIfNecessary()
143 {
144 if(noGeneralFileAccess) { throw new UnsupportedOperationException("light-weight mirror not permitted generic filesystem access"); }
145 }
146
147 /**Minimum interval between rechecks of local properties file content (ms).
148 * Since much of our data comes from the filesystem and many filesystems'
149 * timestamp granularity is about one second, polling much faster than this
150 * is pointless and may simply end up blocking the caller and wasting
151 * CPU pointlessly.
152 * <p>
153 * (Arguably the only way that system properties can be changed is by
154 * programmatic intervention want so we don't want to recheck such values
155 * at all for fear of setting up a covert channel as well as just wasting
156 * CPU time.)
157 * <p>
158 * We pick a prime-ish retry interval to try to avoid collisions with other
159 * regular events.
160 * <p>
161 * A value of a few seconds to a few tens of seconds is probably appropriate.
162 */
163 private static final int MIN_RECHECK_MS = 33007;
164
165 /**The last time that we checked/refreshed the data that we hold.
166 * We update this whenever we do a check whether we changed any values;
167 * this avoids possibly expensive and unnecessary rechecking.
168 * This value is private to us.
169 * <p>
170 * Initially zero to force immediate read on first use of these properties.
171 */
172 private static long lastRecheck;
173
174 /**This routine rechecks and refreshes the data items if necessary.
175 * This is called internally by all getter methods to lazily ensure that
176 * data items are up to date.
177 * <p>
178 * When this has finished it sets lastRecheck and possibly timestamp;
179 * if this fails to complete the update neither (but especially lastRecheck)
180 * may be set.
181 * <p>
182 * When running as a light-weight mirror without filesystem access,
183 * any configuration information is limited to that statically available,
184 * mainly from system properties,
185 * and so this returns immediately.
186 */
187 private static synchronized void _recheck()
188 {
189 if(noGeneralFileAccess) { return; }
190
191 final long now = System.currentTimeMillis();
192
193 if(now - lastRecheck <= MIN_RECHECK_MS)
194 {
195 // Not time for a recheck yet...
196 return;
197 }
198
199 try
200 {
201 // Get location of local properties file, if extant.
202 final File pf = new File(getLocalPropsFilename());
203
204 final long fileTimestamp = pf.lastModified();
205
206 // Warn user if file not found,
207 // and suggest what settings they might use.
208 if(fileTimestamp <= 0)
209 {
210 System.err.println("WARNING: local properties file not found.");
211 System.err.println(" Set name in system property: " + PNAME_LOCAL_PROPS_FILENAME);
212 System.err.println(" or put file at: `" + DEFAULT_LOCAL_PROPS_FILENAME + "' .");
213 }
214
215 // Warn if the timestamp has gone backwards
216 // (or to zero because the file has disappeared).
217 if(fileTimestamp < timestamp)
218 {
219 System.err.println("WARNING: timestamp on local properties file has gone backwards or file has gone: " + pf);
220 }
221
222 // If the file has changed and is newer than our in-memory copy,
223 // read it in.
224 //
225 // If we have problems reading the info,
226 // we abort without updating any timestamps so that can try again
227 // immediately.
228 // We may have updated some of the properties...
229 if(fileTimestamp > timestamp)
230 {
231 Properties props; // The properties that we load.
232 try {
233 // Read the properties file in buffered chunks for efficiency.
234 final InputStream is = new BufferedInputStream(
235 new FileInputStream(pf));
236 try { props = FileTools.loadProperties(is); }
237 finally { is.close(); } // Release the handle ASAP...
238 }
239 catch(final IOException e)
240 {
241 // If we encounter an I/O problem,
242 // log it to System.err,
243 // and return without updating our timestamps.
244 e.printStackTrace();
245 return;
246 }
247
248 // Temporaries to help in parsing.
249 int iTmp;
250 long lTmp;
251 String sTmp;
252
253 // Get dir for read-only configuration files.
254 confDir = MemoryTools.intern(props.getProperty(PNAME_CONF_DIR));
255
256 // Get dir for data files.
257 dataDir = MemoryTools.intern(props.getProperty(PNAME_DATA_DIR));
258
259 // Get dir for cache files.
260 cacheDir = MemoryTools.intern(props.getProperty(PNAME_CACHE_DIR));
261
262 if(IsDebug.isDebug) { System.out.println("Reloaded local properties ("+pf+") with confdir "+confDir+" and dataDir "+dataDir); }
263
264 // Get name of simple password file.
265 simplePassfile = MemoryTools.intern(props.getProperty(PNAME_SIMPLEPASS_FILE));
266
267 // Get name of upload directory.
268 uploadDir = MemoryTools.intern(props.getProperty(PNAME_UPLOAD_DIR));
269
270 // Get name of persistent-state dir.
271 persistentStateDirFromSystemProperties = MemoryTools.intern(props.getProperty(PNAME_PERSISTENT_STATE_DIR));
272
273 // Get mirror tag.
274 // Check thoroughly for syntactic validity before assigning.
275 // If invalid, then set to null instead.
276 final String putativeMirrorTag = props.getProperty(PNAME_MIRROR_TAG);
277 sTmp = validateMirrorTag(putativeMirrorTag);
278 mirrorTag = MemoryTools.intern(sTmp);
279
280
281 iTmp = -1;
282 try { iTmp = Integer.parseInt(props.getProperty(PNAME_MAX_UPLOAD_TOTAL_BYTES, "10123456"), 10); }
283 catch(final Exception e) { } // Ignore errors.
284 uploadMaxBytesTotal = iTmp;
285
286 iTmp = -1;
287 try { iTmp = Integer.parseInt(props.getProperty(PNAME_MAX_UPLOAD_PER_USER_BYTES, "10123456"), 10); }
288 catch(final Exception e) { } // Ignore errors.
289 uploadMaxBytesPerUser = iTmp;
290
291 lTmp = -1;
292 try { lTmp = Long.parseLong(props.getProperty(PNAME_WEBSVR_MAX_EX_CACHE_BYTES, String.valueOf(SYS_PROP_WEBSVR_MAX_EX_CACHE_BYTES_VALUE)), 10); }
293 catch(final Exception e) { } // Ignore errors.
294 WEBSVR_MAX_EX_CACHE_BYTES = lTmp;
295
296 iTmp = -1;
297 try { iTmp = Integer.parseInt(props.getProperty(PNAME_SERVER_SLOWDOWN_FACTOR, "1"), 10); }
298 catch(final Exception e) { } // Ignore errors.
299 SERVER_SLOWDOWN_FACTOR = iTmp;
300
301 // Get raw data protection key, if any.
302 // Any system property takes precedence over a value from the file.
303 sTmp = props.getProperty(PNAME_XFER_HMAC_KEY);
304 try { sTmp = System.getProperty(PNAME_XFER_HMAC_KEY, sTmp); }
305 catch(final SecurityException e) { /* Treat lack of permission as if lack of value. */ }
306 final SecretKey putativeXferKey = processXferKey(sTmp);
307 xferHMACKey = putativeXferKey;
308
309 // Parse the zero-or-more low-power filesystem warning flag file names.
310 sTmp = props.getProperty(PNAME_LOW_POWER_FILE_FLAGS);
311 if(sTmp == null) { lowerPowerFileFlags = Collections.emptyList(); }
312 else
313 {
314 // Separate filenames with whitespace.
315 final String names[] = sTmp.split("[ \t\r\n]+");
316 final List<File> result = new ArrayList<File>(names.length);
317 // Non-absolute pathnames are relative to the localprops dir.
318 for(final String name : names)
319 {
320 File f = new File(name);
321 if(!f.isAbsolute())
322 {
323 final File lpDir = new File(getLocalPropsFilename()).getParentFile();
324 f = (new File(lpDir, name)).getAbsoluteFile();
325 }
326 result.add(f);
327 }
328 // Make unmodifiable result.
329 lowerPowerFileFlags = Collections.unmodifiableList(result);
330 }
331
332 // Parse the zero-or-more low-power flag URL names.
333 sTmp = props.getProperty(PNAME_LOW_POWER_FILE_URIS);
334 if(sTmp == null) { lowerPowerFileURIs = Collections.emptySet(); }
335 // FIXME
336 else
337 {
338 // Separate URIs with whitespace.
339 final String names[] = sTmp.split("[ \t\r\n]+");
340 final Set<URI> result = new HashSet<URI>(1 + names.length*2);
341 for(final String name : names)
342 {
343 try { result.add(new URI(name)); }
344 catch(final URISyntaxException e) { /* Silently discard bad URIs. */ }
345 }
346 // Make unmodifiable result.
347 lowerPowerFileURIs = Collections.unmodifiableSet(result);
348 }
349
350 // Parse/load CPU load thresholds.
351 sTmp = props.getProperty(PNAME_LIGHT_LOAD_FRACTION_MAX);
352 if(sTmp != null)
353 {
354 try
355 {
356 final float f = Float.parseFloat(sTmp);
357 // Must be in range ]0,1[ and thus also not Inf/NaN.
358 if((f > 0) && (f < 1)) { lightLoadMax = new Float(f); }
359 }
360 catch(final NumberFormatException e) { }
361 }
362 sTmp = props.getProperty(PNAME_HEAVY_LOAD_FRACTION_MIN);
363 if(sTmp != null)
364 {
365 try
366 {
367 final float f = Float.parseFloat(sTmp);
368 // Must be greater than getLightLoadMax() and also not Inf/NaN.
369 if(f > 0) { heavyLoadMin = new Float(f); }
370 }
371 catch(final NumberFormatException e) { }
372 }
373
374 // Extract the generic key/value properties, if any.
375 final Map<String,String> g = new HashMap<String, String>();
376 for(final Object keyO : props.keySet())
377 {
378 if(!(keyO instanceof String))
379 { throw new IllegalArgumentException("bad properties key: not String"); }
380 final String key = (String) keyO;
381
382 if(!key.startsWith(GEN_PREFIX)) { continue; }
383
384 final String gKey = key.substring(GEN_PREFIX.length());
385 final String gVal = props.getProperty(key);
386
387 // Validate the values later.
388 g.put(gKey, gVal);
389 }
390 // If no generic keys then save space with shared empty Map.
391 if(g.size() == 0)
392 { gen = Collections.emptyMap(); }
393 else
394 { gen = Collections.unmodifiableMap(g); }
395 }
396
397 // Save the timestamp from the properties file
398 // having successfully completed all that we wanted to.
399 timestamp = fileTimestamp;
400
401 // Update completed.
402 lastRecheck = now;
403 }
404 catch(final SecurityException e)
405 {
406 if(!reportedSecurityException)
407 {
408 System.err.println("NOTE: no access to local properties file: " + e.getMessage());
409 reportedSecurityException = true;
410 }
411
412 // Quietly absorb the security/permissions error,
413 // and defer retrying for the normal length of time.
414 lastRecheck = now;
415 }
416 }
417
418 /**Return computed SecretKey from raw text, or null if none/invalid. */
419 private static SecretKey processXferKey(String rawKeyText)
420 {
421 if(rawKeyText == null) { return(null); }
422 else
423 {
424 rawKeyText = rawKeyText.trim();
425 if(rawKeyText.length() < 16)
426 {
427 System.err.println("[WARNING: too-short xfer key ignored.]");
428 return(null);
429 }
430 else
431 {
432 // OK, key looks superficially OK,
433 // so try to turn it into something usable for HMAC in our tunnel.
434 try
435 {
436 // Extract an entropy-dense form of the (trimmed) key supplied,
437 // ie with all of the goodness, but fewer bytes to process each time.
438 final byte hashed[] = MessageDigest.getInstance(CoreConsts.HASH_SHA1).
439 digest(rawKeyText.getBytes("UTF-8"));
440 // Home-brew immutable/tamper-resistant secret key.
441 return(new SecretKey(){
442 private static final long serialVersionUID = 3350400258615302664L;
443 public String getAlgorithm() { return(""); }
444 public byte[] getEncoded() { return(hashed.clone()); }
445 public String getFormat() { return("RAW"); }
446 });
447 }
448 catch(final Exception e)
449 {
450 System.err.println("[ERROR: LocalProps: unable to create/convert xfer key.]");
451 e.printStackTrace();
452 return(null);
453 }
454 }
455 }
456 }
457
458 /**Validate putative mirror tag; returns non-null only if input is syntactically valid and non-null. */
459 private static String validateMirrorTag(String putativeMirrorTag)
460 {
461 // Reject invalid tags by returning a null.
462 if((putativeMirrorTag == null) || (putativeMirrorTag.length() < 4) || (putativeMirrorTag.length() > 255))
463 { putativeMirrorTag = null; }
464 // Check that the cc is valid; return null if not.
465 else if(!GeoUtils.CCTLD.isSyntaticallyValidCcTLD(putativeMirrorTag.substring(0, 2)))
466 { putativeMirrorTag = null; }
467 // Check that the dash is present; return null if not.
468 else if(putativeMirrorTag.charAt(2) != '-')
469 { putativeMirrorTag = null; }
470 // Check that the remainder of the tag is lower-case or digits; return null if not.
471 else
472 {
473 for(int i = putativeMirrorTag.length(); --i >= 3; )
474 {
475 final char c = putativeMirrorTag.charAt(i);
476 if((c >= 'a') && (c <= 'z')) { continue; }
477 if((c >= '0') && (c <= '9')) { continue; }
478 putativeMirrorTag = null;; // Bad character found so don't use this...
479 }
480 }
481 return(putativeMirrorTag);
482 }
483
484 /**Set true if we've noted a SecurityException trying to read LocalProperties.
485 * Marked volatile for lock-free thread-safe access.
486 */
487 private static volatile boolean reportedSecurityException;
488
489 /**The last time our loaded values changed.
490 * This can be used to cascade changes to parts of the system that depend
491 * on these LocalProps.
492 * <p>
493 * Will never be newer than lastRecheck.
494 * <p>
495 * Initially zero, to indicate no (file-based) values loaded.
496 * <p>
497 * The timestamp is the latest timestamp of any of the files read to
498 * make up the dataset.
499 */
500 private static long timestamp;
501
502 /**Returns the timestamp of the current set of data values.
503 * Forces a (re)load/refresh of data if necessary.
504 * Zero indicates that no (file-based) values have (yet) been loaded.
505 * <p>
506 * The timestamp is the latest timestamp of any of the files read to
507 * make up the dataset.
508 */
509 public static synchronized long getTimestamp()
510 { _recheck(); return(timestamp); }
511
512 /**Default suffix of local properties files, including the dot. */
513 public static final String DEFAULT_PROPS_SUFFIX = ".properties";
514 /**Default filename of local properties file. */
515 public static final String DEFAULT_LOCAL_PROPS_FILENAME =
516 "local" + DEFAULT_PROPS_SUFFIX;
517 /**Name of props file for local config information property. */
518 public static final String PNAME_LOCAL_PROPS_FILENAME = "org.hd.d.pg2k.localPropsFilename";
519 /**Cache of name of props file for local config information; null if no explicit system property value set. */
520 private static final String localPropsFilenameFromSystemProperties;
521 /**Initialise localPropsFilenameFromSystemProperties. */
522 static
523 {
524 String s = null;
525 try { s = System.getProperty(PNAME_LOCAL_PROPS_FILENAME); }
526 catch(final SecurityException e) { /* Treat lack of permission as if lack of value. */ }
527 localPropsFilenameFromSystemProperties = s;
528 }
529 /**Name of props file for local config information; never null.
530 * It may be difficult for the system to run without this parameter
531 * set correctly or if the corresponding file is not correctly set up.
532 * <p>
533 * This will specify a file in the current working directory if none
534 * explicitly specified, and will deliver a warning if it cannot be found.
535 * <p>
536 * This might be bad news if the current working directory changes
537 * or is inappropriate or unhelpful, so set the system property explicitly
538 * if this is the case.
539 * <p>
540 * This does not check that the specified file actually exists.
541 * <p>
542 * We only use this internally; for example this may be inefficient to
543 * continually recheck and we don't necessarily want to publicise the
544 * actual location more than we must!
545 *
546 * @throws UnsupportedOperationException if this is a light-weight mirror
547 * and thus without general filesystem access
548 */
549 private static synchronized String getLocalPropsFilename()
550 {
551 vetoFileAccessIfNecessary();
552
553 // Use explicit value if set.
554 String result = localPropsFilenameFromSystemProperties;
555 // Else try to use canonicalised default name relative to pwd.
556 if(result == null)
557 {
558 try {
559 result = (new File(DEFAULT_LOCAL_PROPS_FILENAME)).getCanonicalPath();
560 }
561 catch(final IOException e)
562 {
563 System.err.println("WARNING: cannot canonicalise local properties filename " + DEFAULT_LOCAL_PROPS_FILENAME);
564 }
565 }
566 // Else try to use relative name directly.
567 if(result == null)
568 { result = DEFAULT_LOCAL_PROPS_FILENAME; }
569 return(result);
570 }
571
572 /**Name of local properties parameter for name of subdirectory for (mainly read-only) data files. */
573 public static final String PNAME_DATA_DIR = "pg2k.data.dir";
574 /**Local cache of name of subdirectory for data files. */
575 private static String dataDir;
576 /**Get name of subdirectory for data files; never null.
577 * If the PNAME_DATA_DIR local property is set then it is used,
578 * else the CoreConsts.FS_DATA_ROOT value is used.
579 * <p>
580 * The returned value is not necessarily canonical
581 * to avoid prodding/waking any filesystems unnecessarily.
582 *
583 * @throws UnsupportedOperationException if this is a light-weight mirror
584 * and thus without general filesystem access
585 */
586 public static synchronized String getDataDir()
587 {
588 vetoFileAccessIfNecessary();
589
590 // Force properties reload if necessary.
591 _recheck();
592
593 // Use local property if set, ...
594 String result = dataDir;
595 // ... else use default from CoreConsts.
596 if(result == null) { result = CoreConsts.FS_DATA_ROOT; }
597
598 // If the directory name is not absolute,
599 // make it relative to the directory that the local properties are in.
600 if(!(new File(result)).isAbsolute())
601 {
602 final File lpDir = new File(getLocalPropsFilename()).getParentFile();
603 result = (new File(lpDir, result)).getAbsolutePath();
604 }
605
606 // Do not canonicalise the path
607 // since doing so may actually force access to the data filesystem
608 // eg mounting and powering up the storage.
609 // // In any case, try to canonicalise the path before returning it.
610 // try { result = (new File(result)).getCanonicalPath(); }
611 // catch(final IOException e) { } // In case of error use non-canonical form...
612
613 return(result);
614 }
615
616 /**Name of local properties parameter for name of subdirectory for read-only conf files. */
617 public static final String PNAME_CONF_DIR = "pg2k.config.dir";
618 /**Local cache of name of subdirectory for read-only conf files. */
619 private static String confDir;
620 /**Get name of subdirectory for read-only conf files; never null.
621 * If the PNAME_CONF_DIR local property is set then it is used,
622 * else the CoreConsts.FS_CONF_ROOT value is used, canonicalised
623 * if possible to give more meaningful error message at least.
624 *
625 * @throws UnsupportedOperationException if this is a light-weight mirror
626 * and thus without general filesystem access
627 */
628 public static synchronized String getConfDir()
629 {
630 vetoFileAccessIfNecessary();
631
632 // Force properties reload if necessary.
633 _recheck();
634
635 // Use local property if set, ...
636 String result = confDir;
637 // ... else use default from CoreConsts.
638 if(result == null) { result = CoreConsts.FS_CONF_ROOT; }
639
640 // If the directory name is not absolute,
641 // make it relative to the directory that the local properties are in.
642 if(!(new File(result)).isAbsolute())
643 {
644 final File lpDir = new File(getLocalPropsFilename()).getParentFile();
645 result = (new File(lpDir, result)).getAbsolutePath();
646 }
647
648 // In any case, try to canonicalise the path before returning it.
649 try { result = (new File(result)).getCanonicalPath(); }
650 catch(final IOException e) { } // In case of error use non-canonical form...
651
652 return(result);
653 }
654
655
656 /**Name of local properties parameter for name of subdirectory for cached data files. */
657 public static final String PNAME_CACHE_DIR = "pg2k.cache.dir";
658 /**Local cache of name of subdirectory for cached data files. */
659 private static String cacheDir;
660 /**Get name of subdirectory for cached files; null if WAR cache default should be used.
661 * If the PNAME_DATA_DIR local property is set then it is used,
662 * <p>
663 * The returned value is not necessarily canonical
664 * to avoid prodding/waking any filesystems unnecessarily.
665 *
666 * @throws UnsupportedOperationException if this is a light-weight mirror
667 * and thus without general filesystem access
668 */
669 public static synchronized String getCacheDir()
670 {
671 vetoFileAccessIfNecessary();
672
673 // Force properties reload if necessary.
674 _recheck();
675
676 // Use local property if set, ...
677 String result = cacheDir;
678 // ... else use default from CoreConsts.
679 if(result == null) { return(null); }
680
681 // If the directory name is not absolute,
682 // make it relative to the directory that the local properties are in.
683 if(!(new File(result)).isAbsolute())
684 {
685 final File lpDir = new File(getLocalPropsFilename()).getParentFile();
686 result = (new File(lpDir, result)).getAbsolutePath();
687 }
688
689 return(result);
690 }
691
692
693
694 /**Name of local properties parameter for name of simple password file. */
695 public static final String PNAME_SIMPLEPASS_FILE =
696 "pg2k.upload.simplepass.file";
697 /**Local cache of name of simple password file; null if none. */
698 private static String simplePassfile;
699 /**Get name of simple password file; null if none.
700 * There is no default for this; if it is not set
701 * then there will be no simple passwords.
702 * <p>
703 * If the path supplied is not absolute,
704 * it is made relative to the result of getConfDir().
705 */
706 public static synchronized String getSimplePassfile()
707 {
708 vetoFileAccessIfNecessary();
709
710 // Force properties reload if necessary.
711 _recheck();
712
713 // Use local property if set, ...
714 String result = simplePassfile;
715 // ... else return null.
716 if(result == null) { return(null); }
717
718 // If the directory name is not absolute,
719 // make it relative to the result of getConfDir().
720 if(!(new File(result)).isAbsolute())
721 {
722 final File confDir = new File(getConfDir());
723 result = (new File(confDir, result)).getAbsolutePath();
724 }
725
726 // In any case, try to canonicalise the path before returning it.
727 try { result = (new File(result)).getCanonicalPath(); }
728 catch(final IOException e) { } // In case of error use non-canonical form...
729
730 return(result);
731 }
732
733
734 /**Name of local properties parameter for name of upload dir. */
735 public static final String PNAME_UPLOAD_DIR = "pg2k.upload.dir";
736 /**Local cache of name of upload dir; null if none specified. */
737 private static String uploadDir;
738 /**Get name of upload dir or null if none was specified.
739 * If the PNAME_UPLOAD_DIR local property is set then it is used.
740 * <p>
741 * If a directory is specified but is not absolute,
742 * it is taken to be relative to the directory the local properties are in.
743 * <p>
744 * Warns if the specified directory is not present,
745 * but returns the path anyway if a path was specified;
746 * the caller should check if it is usable.
747 *
748 * @throws UnsupportedOperationException if this is a light-weight mirror
749 * and thus without general filesystem access
750 */
751 public static synchronized String getUploadDir()
752 {
753 vetoFileAccessIfNecessary();
754
755 // Force properties reload if necessary.
756 _recheck();
757
758 // Use local property if set.
759 String result = uploadDir;
760
761 if(result != null)
762 {
763 // If the directory name is not absolute,
764 // make it relative to the directory the local properties are in.
765 if(!(new File(result)).isAbsolute())
766 {
767 final File lpDir = new File(getLocalPropsFilename()).getParentFile();
768 result = (new File(lpDir, result)).getAbsolutePath();
769 }
770
771 // In any case, try to canonicalise the path before returning it.
772 try { result = (new File(result)).getCanonicalPath(); }
773 catch(final IOException e) { } // In case of error use non-canonical form...
774
775 if(!(new File(result)).isDirectory())
776 {
777 System.err.println("WARNING: upload directory not present: " + result);
778 }
779 }
780
781 return(result);
782 }
783
784
785 /**Name of Web server maximum-upload-space-per-user (bytes) property. */
786 public static final String PNAME_MAX_UPLOAD_PER_USER_BYTES = "pg2k.upload.maxBytes.perUser";
787 /**Web server maximum-upload-space-per-user (bytes). */
788 private static int uploadMaxBytesPerUser;
789 /**Get the Web server maximum-upload-space-per-user (bytes).
790 * Constrained to the range 0 to 1GB, default approx 10MB.
791 */
792 public static synchronized int getUploadMaxBytesPerUser()
793 { return(Math.max(0, Math.min(1123456789, uploadMaxBytesPerUser))); }
794
795 /**Name of Web server maximum-upload-space-total (bytes) property. */
796 public static final String PNAME_MAX_UPLOAD_TOTAL_BYTES = "pg2k.upload.maxBytes.total";
797 /**Web server maximum-upload-space-total (bytes). */
798 private static int uploadMaxBytesTotal;
799 /**Get the Web server maximum-upload-space-total (bytes).
800 * Constrained to the range 0 to 1GB, default approx 10MB.
801 * Also constrained to be no smaller than uploadMaxBytesPerUser.
802 */
803 public static synchronized int getUploadMaxBytesTotal()
804 { return(Math.max(getUploadMaxBytesPerUser(), Math.min(1123456789, uploadMaxBytesTotal))); }
805
806
807
808
809 /**Name of local directory for store of persistent state property. */
810 public static final String PNAME_PERSISTENT_STATE_DIR =
811 "org.hd.d.pg2k.persistentStateDir";
812 /**Local cache of name of local persistent-state directory; null if none. */
813 private static String persistentStateDirFromSystemProperties;
814 /**Relative path from local properties directory to persistent state dir. */
815 public static final String DEFAULT_STATE_DIR = "persistentstate.dat";
816 /**Get name of directory for storing local persistent state; never null.
817 * This is used for storing (especially valuable) data that should survive
818 * over many runs of the WAR/J2EE, but which is local to each WAR instance,
819 * such as usage stats, though may also be used by the master instance to
820 * persist user login data for example.
821 * <p>
822 * If the system property is not set, then we take this relative to the
823 * directory in which we look for the local properties.
824 * <p>
825 * Warns if the specified directory is not present.
826 *
827 * @throws UnsupportedOperationException if this is a light-weight mirror
828 * and thus without general filesystem access
829 */
830 public final static synchronized String getPersistentStateDir()
831 {
832 vetoFileAccessIfNecessary();
833
834 // Force properties reload if necessary.
835 _recheck();
836
837 // Use explicit value if set.
838 String result = persistentStateDirFromSystemProperties;
839
840 // Else try to use relative name directly.
841 if(result == null) { result = DEFAULT_STATE_DIR; }
842
843
844 // If the directory name is not absolute,
845 // make it relative to the directory the local properties are in.
846 if(!(new File(result)).isAbsolute())
847 {
848 final File lpDir = new File(getLocalPropsFilename()).getParentFile();
849 result = (new File(lpDir, result)).getAbsolutePath();
850 }
851
852 // In any case, try to canonicalise the path before returning it.
853 try { result = (new File(result)).getCanonicalPath(); }
854 catch(final IOException e) { } // In case of error use non-canonical form...
855
856 // if(!(new File(result)).isDirectory())
857 // {
858 // System.err.println("WARNING: persistent state directory not present: " + result);
859 // }
860 return(result);
861 }
862
863
864 /**Name of mirror-tag property. */
865 public static final String PNAME_MIRROR_TAG =
866 "org.hd.d.pg2k.mirrorTag";
867 /**Local cache of mirror tag (micro tag if set); null if none. */
868 private static String mirrorTag = microTag;
869 /**Get valid "cc-xxxx" mirror-tag name for this host; null if none.
870 * If this host is a mirror, it has a tag of the form <samp>cc-xxxxx</samp>
871 * where "cc" is a two-letter country code
872 * (as in a top-level ccTLD domain name, eg "uk" rather than "gb")
873 * where the host resides for Internet connectivity purposes,
874 * and "xxxx" is an alphanumeric tag indicating a particular host,
875 * and the whole string is less than 256 characters.
876 * <p>
877 * The entire tag is a lower-case printable ASCII (no whitespace)
878 * alphanumeric value.
879 * <p>
880 * This host should be reachable by the full name of
881 * "mirror-cc-xxxx.gallery.hd.org"
882 * for a tag of "cc-xxxx" and a main domain name of "gallery.hd.org".
883 * <p>
884 * This routine will not return an invalid/unsafe tag.
885 * <p>
886 * This routine does NOT attempt any DNS lookups or in other ways
887 * attempt to verify that the full mirror host name exists or is valid.
888 */
889 public final static synchronized String getMirrorTag()
890 {
891 // Force properties reload if necessary.
892 _recheck();
893
894 return(mirrorTag);
895 }
896
897
898
899 /**Name of local directory for store of thumbnail data property. */
900 public static final String PNAME_PERSISTENT_THUMBNAIL_DIR =
901 "org.hd.d.pg2k.thumbnailDir";
902 /**Local cache of name of local thumbnail directory; null if none. */
903 private static final String thumbnailDirFromSystemProperties;
904 /**Initialise thumbnailDirFromSystemProperties. */
905 static
906 {
907 String s = null;
908 try { s = System.getProperty(PNAME_PERSISTENT_THUMBNAIL_DIR); }
909 catch(final SecurityException e) { /* Treat lack of permission as if lack of value. */ }
910 thumbnailDirFromSystemProperties = s;
911 }
912 /**Relative path from exhibit directory to thumbnails dir. */
913 public static final String DEFAULT_THUMBNAIL_DIR = "../_tn";
914 /**Get name of directory for storing off-line master thumbnails; never null.
915 * This is used for storing off-line computed thumbnails that should survive
916 * over many runs of the WAR/J2EE, mainly intended for read-only
917 * access by the master server, and write access by an off-line tool
918 * to create thumbnails in a less restrictive environment than the WAS.
919 * <p>
920 * If the system property is not set, then we take this relative to the
921 * directory in which we look for the exhibits.
922 * <p>
923 * If the property is set to "." then the exhibit directory is used.
924 */
925 public final static synchronized String getThumbnailRelDir()
926 {
927 vetoFileAccessIfNecessary();
928
929 // Use explicit value if set.
930 String result = thumbnailDirFromSystemProperties;
931 // Else try to use relative name directly.
932 if(result == null)
933 {
934 result = DEFAULT_THUMBNAIL_DIR;
935 }
936 return(result);
937 }
938
939
940 /**Name of Web server maximum generic exhibit cache size (64-bit value, bytes). */
941 public static final String PNAME_WEBSVR_MAX_EX_CACHE_BYTES = "pg2k.websvr.ex.maxcachesize";
942 /**System-property value for maximum generic exhibit cache size; ~4GB default if unset. */
943 private static final long SYS_PROP_WEBSVR_MAX_EX_CACHE_BYTES_VALUE;
944 /**Initialise SYS_PROP_WEBSVR_MAX_EX_CACHE_BYTES_VALUE. */
945 static
946 {
947 long mcb = 1L << 32;
948 try { mcb = Long.getLong(PNAME_WEBSVR_MAX_EX_CACHE_BYTES, mcb); }
949 catch(final SecurityException e) { /* Treat lack of permission as if lack of value. */ }
950 SYS_PROP_WEBSVR_MAX_EX_CACHE_BYTES_VALUE = mcb;
951 }
952 /**Web server maximum generic exhibit cache size (64-bit value, bytes). */
953 private static long WEBSVR_MAX_EX_CACHE_BYTES = SYS_PROP_WEBSVR_MAX_EX_CACHE_BYTES_VALUE; // 4GB default; about 2^16 thumbnails @ 2^16 bytes each.
954 /**Get the Web server maximum generic exhibit cache size (64-bit value, bytes); non-negative.
955 * Constrained to the range approx 0 to 1TB, default approx 2GB.
956 * <p>
957 * This value can be specified with a system property of the same name
958 * if no value is supplied in the properties file.
959 */
960 public static synchronized long getWEBSVR_MAX_EX_CACHE_BYTES()
961 {
962 _recheck();
963
964 // Return constrained value.
965 return(Math.max(0L, Math.min(1123456123456L, WEBSVR_MAX_EX_CACHE_BYTES)));
966 }
967
968
969 /**Name of server slowdown factor; strictly positive. */
970 public static final String PNAME_SERVER_SLOWDOWN_FACTOR = "org.hd.d.pg2k.serverSlowdownFactor";
971 /**Server slowdown factor; defaults to 1 except for cloud (CPU-sensitive) environment where it is higher. */
972 private static int SERVER_SLOWDOWN_FACTOR = isCloudMirrorInstance() ? 2 : 1;
973 /**Get server-slowdown-factor; strictly positive.
974 * Defaults to 1, and no more than about, say, 100.
975 * <p>
976 * Is a factor by which the server under-reports its available capacity, etc.
977 * <p>
978 * When > 1 then this has the effect of:
979 * <ul>
980 * <li>reducing aggressiveness of precaching,
981 * <li>reducing reported available bandwidth,
982 * (and thus the chance of being picked as a user's "preferred" mirror)
983 * </ul>
984 * etc, for sites with constrained or expensive or non-dedicated bandwidth.
985 */
986 public static synchronized int getServerSlowdownFactor()
987 {
988 _recheck();
989
990 // Coerce into reasonable range.
991 return(Math.max(1, Math.min(100, SERVER_SLOWDOWN_FACTOR)));
992 }
993
994
995 /**Name of local properties parameter for HMAC key used to protect data in transit. */
996 public static final String PNAME_XFER_HMAC_KEY = "org.hd.d.pg2k.xferkey";
997 /**Local cache of immutable HMAC (secret) key used to protect data in transit; null if none. */
998 private static SecretKey xferHMACKey;
999 /**Initialise xferHMACKey safely. */
1000 static
1001 {
1002 SecretKey xk = null;
1003 try { xk = processXferKey(System.getProperty(PNAME_XFER_HMAC_KEY)); }
1004 catch(final SecurityException e) { /* Treat lack of permission as if lack of value. */ }
1005 xferHMACKey = xk;
1006 }
1007 /**Get HMAC (secret) key used to protect data in transit; null if none.
1008 * There is no default for this;
1009 * if it is not set then there will be no data protection key.
1010 * <p>
1011 * This first attempts to initialised from the system property
1012 * then any value in the local properties file overrides this.
1013 * <p>
1014 * The key returned is immutable.
1015 * <p>
1016 * The key should be handled with care, since it is sensitive data.
1017 */
1018 public static synchronized SecretKey getXferHMACKey()
1019 {
1020 // Force properties reload if necessary.
1021 _recheck();
1022
1023 // Use local property if set.
1024 return(xferHMACKey);
1025 }
1026
1027 /**Returns true if there is an xfer key available, false otherwise. */
1028 public static boolean hasXferKey() { return(null != getXferHMACKey()); }
1029
1030
1031 /**Name of local properties parameter for Set of low-power file-system flags. */
1032 public static final String PNAME_LOW_POWER_FILE_FLAGS =
1033 "pg2k.lowerPowerFileFlags";
1034 /**Local cache of immutable Set of low-power file-system flags; empty if none. */
1035 private static List<File> lowerPowerFileFlags = Collections.emptyList();
1036 /**Get immutable List of low-power file-system flags; empty if none.
1037 * Will not contain duplicates or nulls.
1038 * <p>
1039 * If more than one entry, the last indicates extreme low-power mode.
1040 * <p>
1041 * The value returned is immutable.
1042 */
1043 public static synchronized List<File> getLowerPowerFileFlags()
1044 {
1045 // Force properties reload if necessary.
1046 _recheck();
1047
1048 // Use local property if set.
1049 return(lowerPowerFileFlags);
1050 }
1051
1052
1053 /**Name of local properties parameter for Set of low-power remote URI http: or https: flags.
1054 * Should only be regarded as absent, ie not low-power mode, if a 404 is obtained.
1055 */
1056 public static final String PNAME_LOW_POWER_FILE_URIS =
1057 "pg2k.lowerPowerURIFlags";
1058 /**Local cache of immutable Set of low-power file-system flags; empty if none. */
1059 private static Set<URI> lowerPowerFileURIs = Collections.emptySet();
1060 /**Get immutable Set of low-power file-system URIs; empty if none.
1061 * The value returned is immutable.
1062 */
1063 public static synchronized Set<URI> getLowerPowerFileURIs()
1064 {
1065 // Force properties reload if necessary.
1066 _recheck();
1067
1068 // Use local property if set.
1069 return(lowerPowerFileURIs);
1070 }
1071
1072
1073 /**Default max fraction of available CPUs in use for system to be considered lightly loaded; positive range ]0.0,1.0[ exclusive.
1074 * Set threshold to allow for functional units possibly being shared (eg HyperThreading),
1075 * and thus different systems have different notions of 'processor'
1076 * for the purposes of our particular workload.
1077 * <p>
1078 * Also avoid significant extra heating or power draw.
1079 * <p>
1080 * A value of between 0.1 and (say) 0.75 is probably reasonable.
1081 */
1082 public static final float DEFAULT_LIGHT_LOAD_FRACTION_MAX = isCloudMirrorInstance() ? 0.1f : 0.45f;
1083
1084 /**Name of CPU light-load threshold. */
1085 public static final String PNAME_LIGHT_LOAD_FRACTION_MAX = "org.hd.d.pg2k.CPU.lightLoadMax";
1086
1087 /**CPU light-load threshold; can be null if none set. */
1088 private static /* final */ Float lightLoadMax;
1089
1090 /**Get CPU light-load threshold; range ]0.0,1.0[ exclusive. */
1091 public static final float getLightLoadMax()
1092 {
1093 _recheck();
1094
1095 final Float ll = lightLoadMax;
1096 final float result = (ll == null) ? DEFAULT_LIGHT_LOAD_FRACTION_MAX : ll;
1097 assert((result > 0) && (result < 1));
1098 return(result);
1099 }
1100
1101 /**Min fraction of available CPUs in use for system to be considered very heavily loaded; strictly positive.
1102 * Can be more than 1.0 indicating queued work for each available CPU,
1103 * and maybe much higher for a usually/acceptably disc-I/O-heavy environment.
1104 * <p>
1105 * Note that there may be immediate visual effects on the Web site of exceeding this,
1106 * such as an obviously-'lite' UI to reduce load,
1107 * and thus doing so prematurely may be annoying to users.
1108 * <p>
1109 * Must be higher than DEFAULT_LIGHT_LOAD_FRACTION_MAX to avoid confusion,
1110 * and this can be easily achieved by having a value greater than 1.0.
1111 * <p>
1112 * Keeping this significantly below 1 will not soak up all available CPU cycles
1113 * but may result in better interactivity/responsiveness.
1114 * <p>
1115 * Note also that on environment such as AWS as at 2011/05
1116 * the value returned by this does not accurately indicate spare CPU
1117 * available to *this* virtual instance (eg may be underlying host's uptime)
1118 * so this should be conservative to help compensate in such environments.
1119 * <p>
1120 * A value of between 0.25 and (say) 5.0 is probably reasonable.
1121 */
1122 public static final float DEFAULT_HEAVY_LOAD_FRACTION_MIN = isCloudMirrorInstance() ? 0.4f : 0.9f;
1123
1124 /**Name of CPU heavy-load threshold. */
1125 public static final String PNAME_HEAVY_LOAD_FRACTION_MIN = "org.hd.d.pg2k.CPU.heavyLoadMin";
1126
1127 /**CPU heavy-load threshold; can be null if none set. */
1128 private static /* final */ Float heavyLoadMin;
1129
1130 /**Get heavy-load threshold; strictly positive and always higher than the light-load-threshold. */
1131 public static float getHeavyLoadMin()
1132 {
1133 _recheck();
1134
1135 final Float hl = heavyLoadMin;
1136 final float result = Math.max((hl == null) ? DEFAULT_HEAVY_LOAD_FRACTION_MIN : hl,
1137 getLightLoadMax() + 0.25f); // Always a little larger than the light-load threshold.
1138 assert(result > 0);
1139 return(result);
1140 }
1141
1142 /**If true then this instance should run in 'fast start' mode.
1143 * Mainly used to influence whether such functions as mustConservePower()
1144 * assume low power mode before testing external flags.
1145 * <p>
1146 * Useful for critical customer-facing machines where start-up latency should be minimised.
1147 * <p>
1148 * Enabled by setting the system property "org.hd.d.pg2k.faststart" to true
1149 * or by being a light-weight cloud instance.
1150 * <p>
1151 * Defaults to false.
1152 * <p>
1153 * Is a method rather than a field to reduce class-initialisation
1154 * circularity issues, so is possibly a little expensive to test continuously.
1155 */
1156 public static boolean fastStartMode()
1157 {
1158 try { return(isCloudMirrorInstance() || Boolean.getBoolean("org.hd.d.pg2k.faststart")); }
1159 catch(final Exception e) { return(false); }
1160 }
1161
1162
1163
1164 /**Maximum number of general properties. */
1165 public static final int MAX_GEN_PROPS = GenProps.MAX_GEN_PROPS;
1166
1167 /**Maximum length of general property key or value (chars). */
1168 public static final int MAX_GEN_LEN = GenProps.MAX_GEN_LEN;
1169
1170 /**Prefix for general properties in file (not needed on lookup). */
1171 public static final String GEN_PREFIX = GenPropsGenNames.GEN_PREFIX;
1172
1173 /**The (immutable) generic properties map; never null. */
1174 private static Map<String,String> gen = Collections.emptyMap();
1175
1176 /**Get the immutable generic properties; never null.
1177 * The keys in this Map here are stripped of the initial GEN_PREFIX
1178 * that appears in the properties file.
1179 */
1180 public static Map<String,String> getGen()
1181 {
1182 _recheck();
1183
1184 return(gen);
1185 }
1186 }