001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.storage;
011:
012: import java.util.*;
013: import org.xml.sax.InputSource;
014: import javax.servlet.ServletContext;
015:
016: import org.mmbase.storage.search.SearchQueryHandler;
017: import org.mmbase.storage.util.*;
018:
019: import org.mmbase.module.core.*;
020: import org.mmbase.clustering.ChangeManager;
021: import org.mmbase.core.CoreField;
022:
023: import org.mmbase.util.ResourceLoader;
024: import org.mmbase.util.transformers.CharTransformer;
025: import org.mmbase.util.transformers.Transformers;
026:
027: import org.mmbase.util.logging.Logger;
028: import org.mmbase.util.logging.Logging;
029:
030: /**
031: * This class contains functionality for retrieving StorageManager instances, which give access to the storage device.
032: * It also provides functionality for setting and retrieving configuration data.
033: * This is an abstract class. You cannot instantiate it. Use the static {@link #newInstance()} or {@link #newInstance(MMBase)}
034: * methods to obtain a factory class.
035: *
036: * @author Pierre van Rooden
037: * @since MMBase-1.7
038: * @version $Id: StorageManagerFactory.java,v 1.33 2008/02/22 12:28:19 michiel Exp $
039: */
040: public abstract class StorageManagerFactory<SM extends StorageManager> {
041:
042: private static final Logger log = Logging
043: .getLoggerInstance(StorageManagerFactory.class);
044:
045: /**
046: * A reference to the MMBase module
047: */
048: protected MMBase mmbase;
049: /**
050: * The class used to instantiate storage managers.
051: * The classname is retrieved from the storage configuration file
052: */
053: protected Class<SM> storageManagerClass;
054:
055: /**
056: * The map with configuration data
057: */
058: protected Map<String, Object> attributes;
059:
060: /**
061: * The list with type mappings
062: */
063: protected List<TypeMapping> typeMappings;
064:
065: /**
066: * The list with objects of which binary data should not be stored in database
067: */
068: protected List<String> storeBinaryAsFileObjects;
069:
070: /**
071: * The ChangeManager object, used to register/broadcast changes to a node or set of nodes.
072: */
073: protected ChangeManager changeManager;
074:
075: /**
076: * The map with disallowed fieldnames and (if given) alternates
077: */
078: protected final SortedMap<String, String> disallowedFields = new TreeMap<String, String>(
079: String.CASE_INSENSITIVE_ORDER);
080:
081: /**
082: * The query handler to use with this factory.
083: * Note: the current handler makes use of the JDBC2NodeInterface and is not optimized for storage: using it means
084: * you call getNodeManager() TWICE.
085: * Have to look into how this should work together.
086: */
087: protected SearchQueryHandler queryHandler;
088:
089: /**
090: * The query handler classes.
091: * Assign a value to this class if you want to set a default query handler.
092: */
093: protected List<Class<?>> queryHandlerClasses = new ArrayList<Class<?>>();
094:
095: /**
096: * @see #getSetSurrogator()
097: */
098: protected CharTransformer setSurrogator = null;
099:
100: /**
101: * @see #getGetSurrogator()
102: */
103: protected CharTransformer getSurrogator = null;
104:
105: /**
106: * The default storage factory class.
107: * This classname is used if you doe not spevify the clasanme in the 'storagemanagerfactory' proeprty in mmabseroot.xml.
108: */
109: static private final Class DEFAULT_FACTORY_CLASS = org.mmbase.storage.implementation.database.DatabaseStorageManagerFactory.class;
110:
111: /**
112: * Obtain the StorageManagerFactory belonging to the indicated MMBase module.
113: * @param mmbase The MMBase module for which to retrieve the storagefactory
114: * @return The StorageManagerFactory
115: * @throws StorageException if the StorageManagerFactory class cannot be located, accessed, or instantiated,
116: * or when something went wrong during configuration of the factory
117: */
118: static public StorageManagerFactory newInstance(MMBase mmbase)
119: throws StorageException {
120: // get the class name for the factory to instantiate
121: String factoryClassName = mmbase
122: .getInitParameter("storagemanagerfactory");
123: // instantiate and initialize the class
124: try {
125: Class factoryClass = DEFAULT_FACTORY_CLASS;
126: if (factoryClassName != null) {
127: factoryClass = Class.forName(factoryClassName);
128: }
129: StorageManagerFactory factory = (StorageManagerFactory) factoryClass
130: .newInstance();
131: factory.init(mmbase);
132: return factory;
133: } catch (ClassNotFoundException cnfe) {
134: throw new StorageFactoryException(cnfe);
135: } catch (IllegalAccessException iae) {
136: throw new StorageFactoryException(iae);
137: } catch (InstantiationException ie) {
138: throw new StorageFactoryException(ie);
139: }
140: }
141:
142: /**
143: * Obtain the storage manager factory belonging to the default MMBase module.
144: * @return The StoragemanagerFactory
145: * @throws StorageException if the StorageManagerFactory class cannot be located, accessed, or instantiated,
146: * or when something went wrong during configuration of the factory
147: */
148: static public StorageManagerFactory newInstance()
149: throws StorageException {
150: // determine the default mmbase module.
151: return newInstance(MMBase.getMMBase());
152: }
153:
154: /**
155: * Initialize the StorageManagerFactory.
156: * This method should be called after instantiation of the factory class.
157: * It is called automatically by {@link #newInstance()} and {@link #newInstance(MMBase)}.
158: * @param mmbase the MMBase instance to which this factory belongs
159: * @throws StorageError when something went wrong during configuration of the factory, or when the storage cannot be accessed
160: */
161: protected final void init(MMBase mmbase) throws StorageError {
162: log.service("initializing Storage Manager factory "
163: + this .getClass().getName());
164: this .mmbase = mmbase;
165: attributes = Collections
166: .synchronizedMap(new HashMap<String, Object>()); // ConcurrentHashMap not possible because null-values are put (TODO)
167: typeMappings = Collections
168: .synchronizedList(new ArrayList<TypeMapping>()); // CopyOnWriteArrayList not possible because Collections.sort is done (TODO)
169: storeBinaryAsFileObjects = Collections
170: .synchronizedList(new ArrayList<String>());
171: changeManager = new ChangeManager();
172: try {
173: log.debug("loading Storage Manager factory "
174: + this .getClass().getName());
175: load();
176: } catch (StorageException se) {
177: // pass exceptions as a StorageError to signal a serious (unrecoverable) error condition
178: log.fatal(se.getMessage() + Logging.stackTrace(se));
179: throw new StorageError(se);
180: }
181: }
182:
183: /**
184: * Return the MMBase module for which the factory was instantiated
185: * @return the MMBase instance
186: */
187: public MMBase getMMBase() {
188: return mmbase;
189: }
190:
191: /**
192: * Instantiate a basic handler object using the specified class.
193: * A basic handler can be any type of class and is dependent on the
194: * factory implementation.
195: * For instance, the database factory expects an
196: * org.mmbase.storage.search.implentation.database.SQLHandler class.
197: * @param handlerClass the class to instantuate teh object with
198: * @return the new handler class
199: */
200: abstract protected Object instantiateBasicHandler(Class handlerClass);
201:
202: /**
203: * Instantiate a chained handler object using the specified class.
204: * A chained handler can be any type of class and is dependent on the
205: * factory implementation.
206: * For instance, the database factory expects an
207: * org.mmbase.storage.search.implentation.database.ChainedSQLHandler class.
208: * @param handlerClass the class to instantuate teh object with
209: * @param previousHandler a handler thatw a sinstantiated previously.
210: * this handler should be passed to the new handler class during or
211: * after constrcution, so the ne whandler can 'chain' any events it cannot
212: * handle to this class.
213: * @return the new handler class
214: */
215: abstract protected Object instantiateChainedHandler(
216: Class handlerClass, Object previousHandler);
217:
218: /**
219: * Instantiate a SearchQueryHandler object using the specified object.
220: * The specified parameter may be an actual SearchQueryHandler object, or it may be a utility class.
221: * For instance, the database factory expects an org.mmbase.storage.search.implentation.database.SQLHandler object,
222: * which is used as a parameter in the construction of the actual SearchQueryHandler class.
223: * @param data the object to instantuate a SearchQueryHandler object with
224: */
225: abstract protected SearchQueryHandler instantiateQueryHandler(
226: Object data);
227:
228: /**
229: * Opens and reads the storage configuration document.
230: * Override this method to add additional configuration code before or after the configuration document is read.
231: * @throws StorageException if the storage could not be accessed or necessary configuration data is missing or invalid
232: */
233: protected void load() throws StorageException {
234: StorageReader<SM> reader = getDocumentReader();
235: if (reader == null) {
236: if (storageManagerClass == null
237: || queryHandlerClasses.size() == 0) {
238: throw new StorageConfigurationException(
239: "No storage reader specified, and no default values available.");
240: } else {
241: log
242: .warn("No storage reader specified, continue using default values.");
243: log.debug("Default storage manager : "
244: + storageManagerClass.getName());
245: log.debug("Default query handler : "
246: + queryHandlerClasses.get(0).getName());
247: return;
248: }
249: }
250:
251: // get the storage manager class
252: Class<SM> configuredClass = reader.getStorageManagerClass();
253: if (configuredClass != null) {
254: storageManagerClass = configuredClass;
255: } else if (storageManagerClass == null) {
256: throw new StorageConfigurationException(
257: "No StorageManager class specified, and no default available.");
258: }
259:
260: // get attributes
261: setAttributes(reader.getAttributes());
262:
263: log
264: .service("get objects with binary data that should not be stored in database");
265: storeBinaryAsFileObjects.addAll(reader
266: .getStoreBinaryAsFileObjects());
267:
268: // get disallowed fields, and add these to the default list
269: disallowedFields.putAll(reader.getDisallowedFields());
270:
271: // add default replacements when DEFAULT_STORAGE_IDENTIFIER_PREFIX is given
272: String prefix = (String) getAttribute(Attributes.DEFAULT_STORAGE_IDENTIFIER_PREFIX);
273: if (prefix != null) {
274: for (Map.Entry<String, String> e : disallowedFields
275: .entrySet()) {
276: String name = e.getKey();
277: String replacement = e.getValue();
278: if (replacement == null) {
279: e.setValue(prefix + "_" + name);
280: }
281: }
282: }
283:
284: log.service("get type mappings");
285: typeMappings.addAll(reader.getTypeMappings());
286: Collections.sort(typeMappings);
287:
288: // get the queryhandler class
289: // has to be done last, as we have to passing the disallowedfields map (doh!)
290: // need to move this to DatabaseStorageManagerFactory
291: List<Class<?>> configuredClasses = reader
292: .getSearchQueryHandlerClasses();
293: if (configuredClasses.size() != 0) {
294: queryHandlerClasses = configuredClasses;
295: } else if (queryHandlerClasses.size() == 0) {
296: throw new StorageConfigurationException(
297: "No SearchQueryHandler class specified, and no default available.");
298: }
299: log.service("Found queryhandlers " + queryHandlerClasses);
300: // instantiate handler(s)
301: Object handler = null;
302: for (Class handlerClass : reader.getSearchQueryHandlerClasses()) {
303: if (handler == null) {
304: handler = instantiateBasicHandler(handlerClass);
305: } else {
306: handler = instantiateChainedHandler(handlerClass,
307: handler);
308: }
309: }
310: // initialize query handler.
311: queryHandler = instantiateQueryHandler(handler);
312:
313: String surr = (String) getAttribute(Attributes.SET_SURROGATOR);
314: if (surr != null && !surr.equals("")) {
315: setSurrogator = Transformers.getCharTransformer(surr, null,
316: "StorageManagerFactory#load", false);
317: }
318:
319: surr = (String) getAttribute(Attributes.GET_SURROGATOR);
320: if (surr != null && !surr.equals("")) {
321: getSurrogator = Transformers.getCharTransformer(surr, null,
322: "StorageManagerFactory#load", false);
323: }
324: }
325:
326: /**
327: * Obtains a StorageManager from the factory.
328: * The instance represents a temporary connection to the datasource -
329: * do not store the result of this call as a static or long-term member of a class.
330: * @return a StorageManager instance
331: * @throws StorageException when the storagemanager cannot be created
332: */
333: public SM getStorageManager() throws StorageException {
334: try {
335: SM storageManager = storageManagerClass.newInstance();
336: storageManager.init(this );
337: return storageManager;
338: } catch (InstantiationException ie) {
339: throw new StorageException(ie);
340: } catch (IllegalAccessException iae) {
341: throw new StorageException(iae);
342: }
343: }
344:
345: // javadoc inherited
346: /**
347: * Obtains a SearchQueryHandler from the factory.
348: * This provides ways to query for data using the SearchQuery interface.
349: * Note that cannot run the querys on a transaction (since SearchQuery does not support them).
350: *
351: * @return a SearchQueryHandler instance
352: * @throws StorageException when the handler cannot be created
353: */
354: public SearchQueryHandler getSearchQueryHandler()
355: throws StorageException {
356: if (queryHandler == null) {
357: throw new StorageException("Cannot obtain a query handler.");
358: } else {
359: return queryHandler;
360: }
361: }
362:
363: /**
364: * Locates and opens the storage configuration document, if available.
365: * The configuration document to open can be set in mmbasereoot (using the storage property).
366: * The property should point to a resource which is to be present in the MMBase classpath.
367: * Overriding factories may provide ways to auto-detect the location of a configuration file.
368: * @throws StorageException if something went wrong while obtaining the document reader
369: * @return a StorageReader instance, or null if no reader has been configured
370: */
371: public StorageReader<SM> getDocumentReader()
372: throws StorageException {
373: // determine storage resource.
374: String storagePath = mmbase.getInitParameter("storage");
375: // use the parameter set in mmbaseroot if it is given
376: if (storagePath != null) {
377: try {
378: InputSource resource = ResourceLoader
379: .getConfigurationRoot().getInputSource(
380: storagePath);
381: if (resource == null) {
382: throw new StorageConfigurationException(
383: "Storage resource '" + storagePath
384: + "' not found.");
385: }
386: return new StorageReader<SM>(this , resource);
387: } catch (java.io.IOException ioe) {
388: throw new StorageConfigurationException(ioe);
389: }
390: } else {
391: // otherwise return null
392: return null;
393: }
394: }
395:
396: /**
397: * Retrieve a map of attributes for this factory.
398: * The attributes are the configuration parameters for the factory.
399: * You cannot change this map, though you can change the attributes themselves.
400: * @return an unmodifiable Map
401: */
402: public Map getAttributes() {
403: return Collections.unmodifiableMap(attributes);
404: }
405:
406: /**
407: * Add a map of attributes for this factory.
408: * The attributes are the configuration parameters for the factory.
409: * The actual content the factory expects is dependent on the implementation.
410: * The attributes are added to any attributes already knwon to the factory.
411: * @param attributes the map of attributes to add
412: */
413: public void setAttributes(Map<String, Object> attributes) {
414: this .attributes.putAll(attributes);
415: log.debug("Database attributes " + this .attributes);
416: }
417:
418: /**
419: * Obtain an attribute from this factory.
420: * Attributes are the configuration parameters for the storagefactory.
421: * @param key the key of the attribute
422: * @return the attribute value, or null if it is unknown
423: */
424: public Object getAttribute(String key) {
425: return attributes.get(key);
426: }
427:
428: /**
429: * Set an attribute of this factory.
430: * Attributes are the configuration parameters for the factory.
431: * The actual content the factory expects is dependent on the implementation.
432: * To invalidate an attribute, you can pass the <code>null</code> value.
433: * @param key the key of the attribute
434: * @param value the value of the attribute
435: */
436: public void setAttribute(String key, Object value) {
437: attributes.put(key, value);
438: }
439:
440: /**
441: * Obtain a scheme from this factory.
442: * Schemes are special attributes, consisting of patterned strings that can be
443: * expanded with arguments.
444: * @param key the key of the attribute
445: * @return the scheme value, or null if it is unknown
446: */
447: public Scheme getScheme(String key) {
448: return getScheme(key, null);
449: }
450:
451: /**
452: * Obtain a scheme from this factory.
453: * Schemes are special attributes, consisting of patterned strings that can be
454: * expanded with arguments.
455: * If no scheme is present, the default pattern is used to create a scheme and add it to the factory.
456: * @param key the key of the attribute
457: * @param defaultPattern the pattern to use for the default scheme, <code>null</code> if there is no default
458: * @return the scheme value, <code>null</code> if there is no scheme
459: */
460: public Scheme getScheme(String key, String defaultPattern) {
461: Scheme scheme = (Scheme) getAttribute(key);
462: if (scheme == null && defaultPattern != null) {
463: if (attributes.containsKey(key))
464: return null;
465: scheme = new Scheme(this , defaultPattern);
466: setAttribute(key, scheme);
467: }
468: return scheme;
469: }
470:
471: /**
472: * Set a scheme of this factory, using a string pattern to base the Scheme on.
473: * Schemes are special attributes, consisting of patterned strings that can be
474: * expanded with arguments.
475: * @param key the key of the scheme
476: * @param pattern the pattern to use for the scheme
477: */
478: public void setScheme(String key, String pattern) {
479: if (pattern == null || pattern.equals("")) {
480: setAttribute(key, null);
481: } else {
482: setAttribute(key, new Scheme(this , pattern));
483: }
484: }
485:
486: /**
487: * Check whether an option was set.
488: * Options are attributes that return a boolean value.
489: * @param key the key of the option
490: * @return <code>true</code> if the option was set
491: */
492: public boolean hasOption(String key) {
493: Object o = getAttribute(key);
494: return (o instanceof Boolean) && ((Boolean) o).booleanValue();
495: }
496:
497: /**
498: * Set an option to true or false.
499: * @param key the key of the option
500: * @param value the value of the option (true or false)
501: */
502: public void setOption(String key, boolean value) {
503: setAttribute(key, Boolean.valueOf(value));
504: }
505:
506: /**
507: * Returns a sorted list of type mappings for this storage.
508: * @return the list of TypeMapping objects
509: */
510: public List<TypeMapping> getTypeMappings() {
511: return Collections.unmodifiableList(typeMappings);
512: }
513:
514: /**
515: * Returns a list of objects of which binary data should be stored in a file.
516: * @return the list of objects of which BLOB fields should not be stored in database.
517: * @since MMBase-1.8.5
518: */
519: public List<String> getStoreBinaryAsFileObjects() {
520: return Collections.unmodifiableList(storeBinaryAsFileObjects);
521: }
522:
523: /**
524: * Returns a map of disallowed field names and their possible alternate values.
525: * @return A Map of disallowed field names
526: */
527: public Map<String, String> getDisallowedFields() {
528: return Collections.unmodifiableSortedMap(disallowedFields);
529: }
530:
531: /**
532: * Sets the map of disallowed Fields.
533: * Unlike setAttributes(), this actually replaces the existing disallowed fields map.
534: */
535: protected void setDisallowedFields(Map disallowedFields) {
536: this .disallowedFields.clear();
537: this .disallowedFields.putAll(disallowedFields);
538: }
539:
540: /**
541: * Obtains the identifier for the basic storage element.
542: * @return the storage-specific identifier
543: * @throws StorageException if the object cannot be given a valid identifier
544: */
545: public Object getStorageIdentifier() throws StorageException {
546: return getStorageIdentifier(mmbase);
547: }
548:
549: /**
550: * Obtains a identifier for an MMBase object.
551: * The default implementation returns the following type of identifiers:
552: * <ul>
553: * <li>For StorageManager: the basename</li>
554: * <li>For MMBase: the String '[basename]_object</li>
555: * <li>For MMObjectBuilder: the String '[basename]_[builder name]'</li>
556: * <li>For Indices: the String '[basename]_[builder name]_[index name]_idx'</li>
557: * <li>For MMObjectNode: the object number as a Integer</li>
558: * <li>For CoreField or String: the field name, or the replacement fieldfname (from the disallowedfields map)</li>
559: * </ul>
560: * Note that 'basename' is a property from the mmbase module, set in mmbaseroot.xml.<br />
561: * You can override this method if you intend to use different ids.
562: *
563: * @see Storable
564: * @param mmobject the MMBase objecty
565: * @return the storage-specific identifier
566: * @throws StorageException if the object cannot be given a valid identifier
567: */
568: public Object getStorageIdentifier(Object mmobject)
569: throws StorageException {
570: String id;
571: if (mmobject instanceof StorageManager) {
572: id = mmbase.getBaseName();
573: } else if (mmobject == mmbase) {
574: id = mmbase.getBaseName() + "_object";
575: } else if (mmobject instanceof MMObjectBuilder) {
576: id = mmbase.getBaseName() + "_"
577: + ((MMObjectBuilder) mmobject).getTableName();
578: } else if (mmobject instanceof MMObjectNode) {
579: return ((MMObjectNode) mmobject).getIntegerValue("number");
580: } else if (mmobject instanceof Index) {
581: id = mmbase.getBaseName() + "_"
582: + ((Index) mmobject).getParent().getTableName()
583: + "_" + ((Index) mmobject).getName() + "_idx";
584: } else if (mmobject instanceof String
585: || mmobject instanceof CoreField) {
586: if (mmobject instanceof CoreField) {
587: id = ((CoreField) mmobject).getName();
588: } else {
589: id = (String) mmobject;
590: }
591: String key = id;
592: if (!hasOption(Attributes.DISALLOWED_FIELD_CASE_SENSITIVE)) {
593: key = key.toLowerCase();
594: }
595: if (disallowedFields.containsKey(key)) {
596: String newid = disallowedFields.get(key);
597: if (newid == null) {
598: if (hasOption(Attributes.ENFORCE_DISALLOWED_FIELDS)) {
599: throw new StorageException(
600: "The name of the field '"
601: + ((CoreField) mmobject)
602: .getName()
603: + "' is disallowed, and no alternate value is available.");
604: }
605: } else {
606: id = newid;
607: }
608: }
609: } else {
610: throw new StorageException(
611: "Cannot obtain identifier for param of type '"
612: + mmobject.getClass().getName() + ".");
613: }
614:
615: String maxIdentifierLength = (String) getAttribute(Attributes.MAX_IDENTIFIER_LENGTH);
616: if (maxIdentifierLength != null
617: && !"".equals(maxIdentifierLength)) {
618: try {
619: int maxlength = Integer.parseInt(maxIdentifierLength);
620: if (id.length() > maxlength) {
621: // Truncate the id, leave 8 characters space to put the hashcode
622: String base = id.substring(0, maxlength - 8);
623: long hashcode = id.hashCode();
624: if (hashcode < 0)
625: hashcode = Integer.MAX_VALUE + hashcode;
626:
627: // This generates a 8-character hex representation of the strings hashcode
628: id = base
629: + (new java.math.BigInteger("" + hashcode))
630: .toString(16);
631: }
632: } catch (NumberFormatException e) {
633: log
634: .warn("Exception parsing the 'max-identifier-length' parameter; ignoring it!");
635: }
636: }
637:
638: String toCase = (String) getAttribute(Attributes.STORAGE_IDENTIFIER_CASE);
639: if ("lower".equals(toCase)) {
640: return id.toLowerCase();
641: } else if ("upper".equals(toCase)) {
642: return id.toUpperCase();
643: } else {
644: return id;
645: }
646: }
647:
648: /**
649: * Returns the ChangeManager utility instance, used to register and broadcast changes to nodes
650: * in the storage layer.
651: * This method is for use by the StorageManager
652: */
653: public ChangeManager getChangeManager() {
654: return changeManager;
655: }
656:
657: /**
658: * Returns the name of the catalog used by this storage (<code>null</code> if no catalog is used).
659: */
660: public String getCatalog() {
661: return null;
662: }
663:
664: /**
665: * Returns the version of this factory implementation.
666: * The factory uses this number to verify whether it can handle storage configuration files
667: * that list version requirements.
668: * @return the version as an integer
669: */
670: abstract public double getVersion();
671:
672: /**
673: * Returns whether transactions, and specifically rollback, is supported in the storage layer.
674: * @return <code>true</code> if trasnactions are supported
675: */
676: abstract public boolean supportsTransactions();
677:
678: /**
679: * Returns a filter which can be used to filter strings taken from storage or <code>null</code> if none defined.
680: * @since MMBase-1.7.4
681: */
682: public CharTransformer getGetSurrogator() {
683: return getSurrogator;
684: }
685:
686: /**
687: * Returns a filter which can be used to filter strings which are to be set into storage or <code>null</code> if none defined.
688: * @since MMBase-1.7.4
689: */
690: public CharTransformer getSetSurrogator() {
691: return setSurrogator;
692: }
693:
694: /**
695: * Returns the offset which must be used in the database. Currently this is based on the system's
696: * default time zone. It is imaginable that can have configuration or database specific details later.
697: * @param time The time at which it is evaluated (summer time issues)
698: * @since MMBase-1.8
699: * @todo experimental
700: */
701: public int getTimeZoneOffset(long time) {
702: return TimeZone.getDefault().getOffset(time);
703: }
704:
705: protected String getDataDir() {
706: String dataDir = mmbase.getInitParameter("datadir");
707: if (dataDir == null || dataDir.equals("")) {
708: ServletContext sc = MMBaseContext.getServletContext();
709: dataDir = sc != null ? sc.getRealPath("/WEB-INF/data")
710: : null;
711: if (dataDir == null) {
712: dataDir = System.getProperty("user.dir")
713: + java.io.File.separator + "data";
714: }
715: }
716: log.info("MMBase data dir: " + dataDir);
717: return dataDir;
718: }
719:
720: }
|