001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2007, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.factory;
017:
018: // J2SE dependencies
019: import java.awt.RenderingHints;
020: import java.util.Iterator;
021: import java.util.HashMap;
022: import java.util.Map;
023:
024: import javax.naming.InitialContext;
025: import javax.naming.NamingException;
026: import javax.swing.event.ChangeEvent;
027: import javax.swing.event.ChangeListener;
028: import javax.swing.event.EventListenerList;
029:
030: // Geotools dependencies
031: import org.geotools.resources.XMath;
032: import org.geotools.resources.Arguments;
033: import org.geotools.resources.Utilities;
034: import org.geotools.resources.i18n.ErrorKeys;
035: import org.geotools.resources.i18n.Errors;
036: import org.geotools.util.logging.CommonsLoggerFactory;
037: import org.geotools.util.logging.Log4JLoggerFactory;
038: import org.geotools.util.logging.LoggerFactory;
039: import org.geotools.util.logging.Logging;
040: import org.geotools.util.Version;
041:
042: /**
043: * Static methods relative to the global GeoTools configuration. GeoTools can be configured
044: * in a system-wide basis through {@linkplain System#getProperties system properties}, some
045: * of them are declared as {@link String} constants in this class.
046: * <p>
047: * There are two aspects to the configuration of GeoTools:
048: * <ul>
049: * <li>Default Settings: Are handled as the Hints returned by {@link getDefaultHints()}, the default values
050: * can be provided by your code, or specified using system properties.
051: * <li>Integration JNDI: Telling the GeoTools library about the facilities of your application, or application
052: * container takes several forms. This class provides the {@link initContext( InitialContext ) } method
053: * allowing you to tell GeoTools about the JNDI context you would like it to use.
054: * <li>Intergration Plugins: If you are hosting GeoTools in a alternate plugin system such as Spring or OSGi you will need to hunt down the FactoryFinders and
055: * register additional "FactoryIterators" you would like GeoTools to search using the {@link addFactoryIteratorProvider} method.
056: * </ul>
057: * <h3>JNDI Integration</h3>
058: * This class provides a {@linkplain InitialContext initial context} for <cite>Java Naming and Directory
059: * Interfaces</cite> (JNDI) in Geotools. This classes provides a central place where initial
060: * context can been found for the Geotools library. This context is used for example by the
061: * {@linkplain org.geotools.referencing.factory.epsg.ThreadedEpsgFactory EPSG factory} in order to
062: * find connection parameters to an EPSG database. Using JNDI, such connection parameters can
063: * be set in a J2EE environment.
064: *
065: * @since 2.4
066: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/factory/GeoTools.java $
067: * @version $Id: GeoTools.java 29058 2008-02-03 17:47:07Z desruisseaux $
068: * @author Jody Garnett
069: * @author Martin Desruisseaux
070: */
071: public final class GeoTools {
072: /**
073: * The current GeoTools version. The separator character must be the dot.
074: */
075: private static final Version VERSION = new Version("2.4.SNAPSHOT");
076:
077: /**
078: * Object to inform about system-wide configuration changes.
079: * We use the Swing utility listener list since it is lightweight and thread-safe.
080: * Note that it doesn't involve any dependency to the remaining of Swing library.
081: */
082: private static final EventListenerList LISTENERS = new EventListenerList();
083:
084: /**
085: * The bindings between {@linkplain System#getProperties system properties} and
086: * a hint key. This field must be declared before any call to the {@link #bind}
087: * method.
088: */
089: private static final Map/*<String, RenderingHints.Key>*/BINDINGS = new HashMap();
090:
091: /**
092: * The {@linkplain System#getProperty(String) system property} key for the default value to be
093: * assigned to the {@link Hints#CRS_AUTHORITY_EXTRA_DIRECTORY CRS_AUTHORITY_EXTRA_DIRECTORY}
094: * hint.
095: *
096: * @see Hints#CRS_AUTHORITY_EXTRA_DIRECTORY
097: * @see #getDefaultHints
098: */
099: public static final String CRS_AUTHORITY_EXTRA_DIRECTORY = "org.geotools.referencing.crs-directory";
100: static {
101: bind(CRS_AUTHORITY_EXTRA_DIRECTORY,
102: Hints.CRS_AUTHORITY_EXTRA_DIRECTORY);
103: }
104:
105: /**
106: * The {@linkplain System#getProperty(String) system property} key for the default
107: * value to be assigned to the {@link Hints#EPSG_DATA_SOURCE EPSG_DATA_SOURCE} hint.
108: *
109: * @see Hints#EPSG_DATA_SOURCE
110: * @see #getDefaultHints
111: */
112: public static final String EPSG_DATA_SOURCE = "org.geotools.referencing.epsg-datasource";
113: static {
114: bind(EPSG_DATA_SOURCE, Hints.EPSG_DATA_SOURCE);
115: }
116:
117: /**
118: * The {@linkplain System#getProperty(String) system property} key for the default
119: * value to be assigned to the {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER
120: * FORCE_LONGITUDE_FIRST_AXIS_ORDER} hint.
121: *
122: * This setting can provide a transition path for projects expecting a (<var>longitude</var>,
123: * <var>latitude</var>) axis order on a system-wide level. Application developpers can set the
124: * default value as below:
125: *
126: * <blockquote><pre>
127: * System.setProperty(FORCE_LONGITUDE_FIRST_AXIS_ORDER, "true");
128: * </pre></blockquote>
129: *
130: * Note that this system property applies mostly to the default EPSG factory. Most other
131: * factories ({@code "CRS"}, {@code "AUTO"}, <cite>etc.</cite>) don't need this property
132: * since they use (<var>longitude</var>, <var>latitude</var>) axis order by design.
133: *
134: * @see Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER
135: * @see #getDefaultHints
136: */
137: public static final String FORCE_LONGITUDE_FIRST_AXIS_ORDER = "org.geotools.referencing.forceXY";
138: static {
139: bind(FORCE_LONGITUDE_FIRST_AXIS_ORDER,
140: Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER);
141: }
142:
143: /**
144: * The initial context. Will be created only when first needed.
145: */
146: private static InitialContext context;
147:
148: /**
149: * Do not allow instantiation of this class.
150: */
151: private GeoTools() {
152: }
153:
154: /**
155: * Binds the specified {@linkplain System#getProperty(String) system property}
156: * to the specified key. Only one key can be binded to a given system property.
157: * However the same key can be binded to more than one system property names,
158: * in which case the extra system property names are aliases.
159: *
160: * @param property The system property.
161: * @param key The key to bind to the system property.
162: * @throws IllegalArgumentException if an other key is already bounds
163: * to the given system property.
164: */
165: private static void bind(final String property,
166: final RenderingHints.Key key) {
167: synchronized (BINDINGS) {
168: final RenderingHints.Key old = (RenderingHints.Key) BINDINGS
169: .put(property, key);
170: if (old == null) {
171: return;
172: }
173: // Roll back
174: BINDINGS.put(property, old);
175: }
176: throw new IllegalArgumentException(Errors.format(
177: ErrorKeys.ILLEGAL_ARGUMENT_$2, "property", property));
178: }
179:
180: /**
181: * Reports back the version of GeoTools being used.
182: */
183: public static Version getVersion() {
184: return VERSION;
185: }
186:
187: /**
188: * Sets the global {@linkplain LoggerFactory logger factory}.
189: *
190: * This method is the same as {@code Logging.GEOTOOLS.setLoggerFactory(factory)}.
191: * GeoTools ships with support for
192: * <A HREF="http://jakarta.apache.org/commons/logging/">Commons-logging</A> and
193: * <A HREF="http://logging.apache.org/log4j/">log4j</A>. This method exists to allow you
194: * supply your own implementation (this is sometimes required when using a GeoTools
195: * application in an exotic environment like Eclipse, OC4J or your application).
196: *
197: * @see Logging#setLoggerFactory(LoggerFactory)
198: *
199: * @since 2.4
200: */
201: public void setLoggerFactory(final LoggerFactory factory) {
202: Logging.GEOTOOLS.setLoggerFactory(factory);
203: }
204:
205: /**
206: * Initializes GeoTools for use. This convenience method performs various tasks (more may
207: * be added in the future), including setting up the {@linkplain java.util.logging Java
208: * logging framework} in one of the following states:
209: * <p>
210: * <ul>
211: * <li>If the <A HREF="http://jakarta.apache.org/commons/logging/">Commons-logging</A>
212: * framework is available, then every logging message in the {@code org.geotools}
213: * namespace sent to the Java {@linkplain java.util.logging.Logger logger} are
214: * redirected to Commons-logging.</li>
215: *
216: * <li>Otherwise if the <A HREF="http://logging.apache.org/log4j">Log4J</A> framework is
217: * available, then every logging message in the {@code org.geotools} namespace sent
218: * to the Java {@linkplain java.util.logging.Logger logger} are redirected to Log4J.</li>
219: *
220: * <li>Otherwise, the Java logging {@linkplain java.util.logging.Formatter formatter} for
221: * console output is replaced by a {@linkplain org.geotools.util.logging.MonolineFormatter
222: * monoline formatter}.</li>
223: * </ul>
224: * <p>
225: * In addition, the {@linkplain #getDefaultHints default hints} are initialized to the
226: * specified {@code hints}.
227: * <p>
228: * Note that invoking this method is usually <strong>not</strong> needed for proper working
229: * of the Geotools library. It is just a convenience method for overwriting some Java and
230: * Geotools default settings in a way that seems to be common in server environment. Such
231: * overwriting may not be wanted for every situations.
232: * <p>
233: * Example of typical invocation in a Geoserver environment:
234: *
235: * <blockquote><pre>
236: * Hints hints = new Hints(null);
237: * hints.put({@linkplain Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER}, Boolean.TRUE);
238: * hints.put({@linkplain Hints#FORCE_AXIS_ORDER_HONORING}, "http");
239: * GeoTools.init(hints);
240: * </pre></blockquote>
241: *
242: * @see Logging#setLoggerFactory(String)
243: * @see Logging#forceMonolineConsoleOutput
244: * @see Hints#putSystemDefault
245: * @see #getDefaultHints
246: */
247: public static void init(final Hints hints) {
248: final Logging log = Logging.GEOTOOLS;
249: try {
250: log
251: .setLoggerFactory("org.geotools.util.logging.CommonsLoggerFactory");
252: } catch (ClassNotFoundException commonsException) {
253: try {
254: log
255: .setLoggerFactory("org.geotools.util.logging.Log4JLoggerFactory");
256: } catch (ClassNotFoundException log4jException) {
257: // Nothing to do, we already tried our best.
258: }
259: }
260: // If java logging is used, force monoline console output.
261: if (log.getLoggerFactory() == null) {
262: log.forceMonolineConsoleOutput();
263: }
264: if (hints != null) {
265: Hints.putSystemDefault(hints);
266: }
267: }
268:
269: /**
270: * Forces the initial context for test cases, or as needed.
271: *
272: * @see #getInitialContext
273: *
274: * @since 2.4
275: */
276: public static synchronized void init(
277: final InitialContext applicationContext) {
278: context = applicationContext;
279: }
280:
281: /**
282: * Scans {@linkplain System#getProperties system properties} for any property keys
283: * defined in this class, and add their values to the specified map of hints. For
284: * example if the {@value #FORCE_LONGITUDE_FIRST_AXIS_ORDER} system property is
285: * defined, then the {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER
286: * FORCE_LONGITUDE_FIRST_AXIS_ORDER} hint will be added to the set of hints.
287: *
288: * @return {@code true} if at least one hint changed as a result of this scan,
289: * or {@code false} otherwise.
290: */
291: static boolean scanForSystemHints(final Hints hints) {
292: assert Thread.holdsLock(hints);
293: boolean changed = false;
294: synchronized (BINDINGS) {
295: for (final Iterator it = BINDINGS.entrySet().iterator(); it
296: .hasNext();) {
297: final Map.Entry entry = (Map.Entry) it.next();
298: final String propertyKey = (String) entry.getKey();
299: final String property;
300: try {
301: property = System.getProperty(propertyKey);
302: } catch (SecurityException e) {
303: unexpectedException(e);
304: continue;
305: }
306: if (property != null) {
307: /*
308: * Converts the system property value from String to Object (java.lang.Boolean
309: * or java.lang.Number). We perform this conversion only if the key is exactly
310: * of kind Hints.Key, not a subclass like ClassKey, in order to avoid useless
311: * class loading on 'getValueClass()' method invocation (ClassKey don't make
312: * sense for Boolean and Number, which are the only types that we convert here).
313: */
314: Object value = property;
315: final RenderingHints.Key hintKey = (RenderingHints.Key) entry
316: .getValue();
317: if (hintKey.getClass().equals(Hints.Key.class)) {
318: final Class type = ((Hints.Key) hintKey)
319: .getValueClass();
320: if (type.equals(Boolean.class)) {
321: value = Boolean.valueOf(property);
322: } else if (Number.class.isAssignableFrom(type))
323: try {
324: value = XMath.valueOf(type, property);
325: } catch (NumberFormatException e) {
326: unexpectedException(e);
327: continue;
328: }
329: }
330: final Object old;
331: try {
332: old = hints.put(hintKey, value);
333: } catch (IllegalArgumentException e) {
334: // The property value is illegal for this hint.
335: unexpectedException(e);
336: continue;
337: }
338: if (!changed && !Utilities.equals(old, value)) {
339: changed = true;
340: }
341: }
342: }
343: }
344: return changed;
345: }
346:
347: /**
348: * Logs an exception as if it originated from {@link Hints#scanSystemProperties},
349: * since it is the public API that may invokes this method.
350: */
351: private static void unexpectedException(final Exception exception) {
352: Logging.unexpectedException("org.geotools.factory",
353: Hints.class, "scanSystemProperties", exception);
354: }
355:
356: /**
357: * Returns the default set of hints used for the various utility classes.
358: * This default set is determined by:
359: * <p>
360: * <ul>
361: * <li>The {@linplain System#getProperties system properties} available. Some property
362: * keys are enumerated in the {@link GeoTools} class.</li>
363: * <li>Any hints added by call to the {@link Hints#putSystemDefault}
364: * or {@link #init} method.</li>
365: * </ul>
366: * <p>
367: * <b>Long term plan:</b>
368: * We would like to transition the utility classes to being injected with their
369: * required factories, either by taking Hints as part of their constructor, or
370: * otherwise. Making this change would be a three step process 1) create instance
371: * methods for each static final class method 2) create an singleton instance of the
372: * class 3) change each static final class method into a call to the singleton. With
373: * this in place we could then encourage client code to make use of utility class
374: * instances before eventually retiring the static final methods.
375: *
376: * @return A copy of the default hints. It is safe to add to it.
377: */
378: public static Hints getDefaultHints() {
379: return Hints.getDefaults(false);
380: }
381:
382: /**
383: * Returns the default initial context.
384: *
385: * @param hints An optional set of hints, or {@code null} if none.
386: * @return The initial context (never {@code null}).
387: * @throws NamingException if the initial context can't be created.
388: *
389: * @see #init(InitialContext)
390: *
391: * @since 2.4
392: */
393: public static synchronized InitialContext getInitialContext(
394: final Hints hints) throws NamingException {
395: if (context == null) {
396: context = new InitialContext();
397: }
398: return context;
399: }
400:
401: /**
402: * Adds an alternative way to search for factory implementations. {@link FactoryRegistry} has
403: * a default mechanism bundled in it, which uses the content of all {@code META-INF/services}
404: * directories found on the classpath. This {@code addFactoryIteratorProvider} method allows
405: * to specify additional discovery algorithms. It may be useful in the context of some
406: * frameworks that use the <cite>constructor injection</cite> pattern, like the
407: * <a href="http://www.springframework.org/">Spring framework</a>.
408: */
409: public static void addFactoryIteratorProvider(
410: final FactoryIteratorProvider provider) {
411: Factories.addFactoryIteratorProvider(provider);
412: }
413:
414: /**
415: * Removes a provider that was previously {@linkplain #addFactoryIteratorProvider added}.
416: * Note that factories already obtained from the specified provider will not be
417: * {@linkplain FactoryRegistry#deregisterServiceProvider deregistered} by this method.
418: */
419: public static void removeFactoryIteratorProvider(
420: final FactoryIteratorProvider provider) {
421: Factories.removeFactoryIteratorProvider(provider);
422: }
423:
424: /**
425: * Adds the specified listener to the list of objects to inform when system-wide
426: * configuration changed.
427: */
428: public static void addChangeListener(final ChangeListener listener) {
429: removeChangeListener(listener); // Ensure singleton.
430: LISTENERS.add(ChangeListener.class, listener);
431: }
432:
433: /**
434: * Removes the specified listener from the list of objects to inform when system-wide
435: * configuration changed.
436: */
437: public static void removeChangeListener(
438: final ChangeListener listener) {
439: LISTENERS.remove(ChangeListener.class, listener);
440: }
441:
442: /**
443: * Informs every listeners that system-wide configuration changed.
444: */
445: public static void fireConfigurationChanged() {
446: final ChangeEvent event = new ChangeEvent(GeoTools.class);
447: final Object[] listeners = LISTENERS.getListenerList();
448: for (int i = 0; i < listeners.length; i += 2) {
449: if (listeners[i] == ChangeListener.class) {
450: ((ChangeListener) listeners[i + 1]).stateChanged(event);
451: }
452: }
453: }
454:
455: /**
456: * Reports the GeoTools {@linkplain #getVersion version} number to the
457: * {@linkplain System#out standard output stream}.
458: */
459: public static void main(String[] args) {
460: final Arguments arguments = new Arguments(args);
461: args = arguments.getRemainingArguments(0);
462: arguments.out.print("GeoTools version ");
463: arguments.out.println(getVersion());
464: final Hints hints = getDefaultHints();
465: if (hints != null && !hints.isEmpty()) {
466: arguments.out.println(hints);
467: }
468: }
469: }
|