org.deegree.cs.persistence.CRSManager.java Source code

Java tutorial

Introduction

Here is the source code for org.deegree.cs.persistence.CRSManager.java

Source

//$HeadURL: svn+ssh://lbuesching@svn.wald.intevation.de/deegree/base/trunk/resources/eclipse/files_template.xml $
/*----------------------------------------------------------------------------
 This file is part of deegree, http://deegree.org/
 Copyright (C) 2001-2010 by:
 - Department of Geography, University of Bonn -
 and
 - lat/lon GmbH -
    
 This library is free software; you can redistribute it and/or modify it under
 the terms of the GNU Lesser General Public License as published by the Free
 Software Foundation; either version 2.1 of the License, or (at your option)
 any later version.
 This library is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 details.
 You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    
 Contact information:
    
 lat/lon GmbH
 Aennchenstr. 19, 53177 Bonn
 Germany
 http://lat-lon.de/
    
 Department of Geography, University of Bonn
 Prof. Dr. Klaus Greve
 Postfach 1147, 53001 Bonn
 Germany
 http://www.geographie.uni-bonn.de/deegree/
    
 e-mail: info@deegree.org
 ----------------------------------------------------------------------------*/
package org.deegree.cs.persistence;

import static java.lang.System.currentTimeMillis;
import static org.deegree.commons.xml.stax.XMLStreamUtils.closeQuietly;

import java.io.File;
import java.io.FilenameFilter;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;

import org.apache.commons.io.IOUtils;
import org.deegree.commons.tom.Object;
import org.deegree.commons.tom.ReferenceResolver;
import org.deegree.commons.tom.ows.CodeType;
import org.deegree.commons.xml.stax.XMLStreamUtils;
import org.deegree.cs.CRSCodeType;
import org.deegree.cs.CRSResource;
import org.deegree.cs.coordinatesystems.ICRS;
import org.deegree.cs.exceptions.CRSConfigurationException;
import org.deegree.cs.exceptions.CRSStoreException;
import org.deegree.cs.exceptions.TransformationException;
import org.deegree.cs.exceptions.UnknownCRSException;
import org.deegree.cs.i18n.Messages;
import org.deegree.cs.refs.coordinatesystem.CRSRef;
import org.deegree.cs.transformations.Transformation;
import org.deegree.cs.transformations.TransformationFactory;
import org.deegree.workspace.Destroyable;
import org.deegree.workspace.Initializable;
import org.deegree.workspace.Workspace;
import org.deegree.workspace.standard.DefaultWorkspace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Entry point for creating and retrieving {@link CRSStore} and {@link CRSStoreProvider} instances.
 * 
 * @author <a href="mailto:buesching@lat-lon.de">Lyn Buesching</a>
 * @author last edited by: $Author: lyn $
 * 
 * @version $Revision: $, $Date: $
 */
public class CRSManager implements Initializable, Destroyable {

    private static Logger LOG = LoggerFactory.getLogger(CRSManager.class);

    private static Map<String, CRSStoreProvider> nsToProvider = null;

    private static Map<String, CRSStore> idToCRSStore = Collections
            .synchronizedMap(new HashMap<String, CRSStore>());

    // store ids in order of requesting, workspace stores should overwrite the default store!
    private static List<String> storeIds = Collections.synchronizedList(new LinkedList<String>());

    private static Map<String, TransformationFactory> idToTransF = new HashMap<String, TransformationFactory>();

    private Workspace workspace;

    private static boolean defaultInitialized = false;

    static {
        new CRSManager().initDefault();
    }

    private void initDefault() {
        synchronized (CRSManager.class) {
            if (defaultInitialized) {
                return;
            }
            LOG.info("--------------------------------------------------------------------------------");
            LOG.info("No 'crs' directory -- use default configuration.");
            LOG.info("--------------------------------------------------------------------------------");

            URL defaultConfig = CRSManager.class.getResource("default.xml");
            try {
                handleConfigFile(defaultConfig, false);
                defaultInitialized = true;
            } catch (Throwable t) {
                LOG.error("The default configuration could not be loaded: " + t.getMessage());
            }
        }
    }

    @Override
    public void init(Workspace workspace) {
        this.workspace = workspace;
        initDefault();
        init(new File(((DefaultWorkspace) workspace).getLocation(), "crs"));
    }

    @Override
    public void destroy(Workspace workspace) {
        LOG.info("Clear CRS store and transformation map");
        idToCRSStore.clear();
        idToTransF.clear();
        storeIds.clear();
        defaultInitialized = false;
        new CRSManager().initDefault();
    }

    /**
     * Initializes the {@link CRSManager} by loading all crs store configurations from the given directory. If null, or
     * directory does not exist the default will be used.
     * 
     * @param crsDir
     */
    public void init(File crsDir) {
        initDefault();
        if (crsDir != null && crsDir.exists()) {
            LOG.info("--------------------------------------------------------------------------------");
            LOG.info("Setting up crs stores.");
            LOG.info("--------------------------------------------------------------------------------");

            File[] crsConfigFiles = crsDir.listFiles(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    return name.toLowerCase().endsWith(".xml");
                }
            });

            for (File crsConfigFile : crsConfigFiles) {
                try {
                    boolean prefer = true;
                    if ("default.xml".equals(crsConfigFile.getName())) {
                        prefer = false;
                        remove("default");
                        LOG.info("CRS store " + crsConfigFile + " overwrites internal configuration!");
                    }
                    handleConfigFile(crsConfigFile.toURI().toURL(), prefer);
                } catch (Throwable t) {
                    LOG.error("Unable to read config file '" + crsConfigFile + "'.", t);
                }
            }
            LOG.info("");
        } else {
            LOG.info("Could not set up CRS stores: CRS workspace directory " + crsDir
                    + " is null or does not exist.");
        }
    }

    private void handleConfigFile(URL crsConfigFile, boolean prefer) {
        String fileName = crsConfigFile.getFile();
        int fileNameStart = fileName.lastIndexOf('/') + 1;
        // 4 is the length of ".xml"
        String crsId = fileName.substring(fileNameStart, fileName.length() - 4);
        LOG.info("Setting up crs store '" + crsId + "' from file '" + fileName + "'..." + "");
        try {
            CRSStore crss = create(crsConfigFile.toURI().toURL());
            registerAndInit(crss, crsId, prefer);
        } catch (Exception e) {
            LOG.error("Error creating crs store: " + e.getMessage());
            LOG.trace("Stack trace:", e);
        }
    }

    /**
     * Returns an uninitialized {@link CRSStore} instance that's created from the specified CRSStore configuration
     * document.
     * 
     * @param configURL
     *            URL of the configuration document, must not be <code>null</code>
     * @return corresponding {@link CRSStore} instance, not yet initialized, never <code>null</code>
     * @throws CRSStoreException
     *             if the creation fails, e.g. due to a configuration error
     */
    public synchronized CRSStore create(URL configURL) throws CRSStoreException {
        String namespace = null;
        XMLStreamReader xmlReader = null;
        InputStream urlStream = null;
        try {
            urlStream = configURL.openStream();
            xmlReader = XMLInputFactory.newInstance().createXMLStreamReader(urlStream);
            XMLStreamUtils.nextElement(xmlReader);
            namespace = xmlReader.getNamespaceURI();
        } catch (Exception e) {
            String msg = Messages.get("CRSManager.CREATING_STORE_FAILED", configURL);
            LOG.error(msg);
            throw new CRSStoreException(msg);
        } finally {
            closeQuietly(xmlReader);
            IOUtils.closeQuietly(urlStream);
        }
        LOG.debug("Config namespace: '" + namespace + "'");
        CRSStoreProvider provider = getProviders().get(namespace);
        if (provider == null) {
            String msg = Messages.get("CRSManager.MISSING_PROVIDER", namespace, configURL);
            LOG.error(msg);
            throw new CRSStoreException(msg);
        }
        return provider.getCRSStore(configURL, workspace);
    }

    /**
     * Returns all available {@link CRSStore} providers.
     * 
     * @return all available providers, keys: config namespace, value: provider instance
     */
    private synchronized Map<String, CRSStoreProvider> getProviders() {
        if (nsToProvider == null) {
            nsToProvider = new HashMap<String, CRSStoreProvider>();
            try {
                ServiceLoader<CRSStoreProvider> loaded;
                if (workspace != null) {
                    loaded = ServiceLoader.load(CRSStoreProvider.class, workspace.getModuleClassLoader());
                } else {
                    loaded = ServiceLoader.load(CRSStoreProvider.class);
                }
                for (CRSStoreProvider provider : loaded) {
                    LOG.debug("CRS store provider: " + provider + ", namespace: " + provider.getConfigNamespace());
                    if (nsToProvider.containsKey(provider.getConfigNamespace())) {
                        LOG.error("Multiple crs store providers for config namespace: '"
                                + provider.getConfigNamespace() + "' on classpath -- omitting provider '"
                                + provider.getClass().getName() + "'.");
                        continue;
                    }
                    nsToProvider.put(provider.getConfigNamespace(), provider);
                }
            } catch (Exception e) {
                LOG.error(e.getMessage(), e);
            }

        }
        return nsToProvider;
    }

    private static void registerAndInit(CRSStore crss, String id, boolean prefer) throws CRSStoreException {
        if (id != null) {
            if (idToCRSStore.containsKey(id)) {
                throw new CRSStoreException(Messages.getMessage("CRSManager.DUPLICATE_ID", id));
            }
            LOG.info(
                    "Registering global crs store with id '" + id + "', type: '" + crss.getClass().getName() + "'");
            idToTransF.put(id, new TransformationFactory(crss));
            idToCRSStore.put(id, crss);
            if (prefer) {
                storeIds.add(0, id);
            } else {
                storeIds.add(id);
            }
            crss.init();
        }
    }

    /**
     * Returns all active {@link CRSStore}s.
     * 
     * @return the {@link CRSStore}s instance, may be empty but never <code>null</code>
     */
    public static Collection<CRSStore> getAll() {
        return idToCRSStore.values();
    }

    /**
     * Returns the {@link CRSStore} instance with the specified identifier or <code>null</code> if an assigned
     * {@link CRSStore} is missing
     * 
     * @param id
     *            identifier of the {@link CRSStore} instance
     * @return the corresponding {@link CRSStore} instance or the default {@link CRSStore} instance if no such instance
     *         has been created or <code>null</code> if the default one could also not be created.
     */
    public static CRSStore get(String id) {
        return idToCRSStore.get(id);
    }

    public static Collection<String> getCrsStoreIds() {
        return idToCRSStore.keySet();
    }

    /*********************************************/
    /**
     * This method allows to get all {@link ICRS} from all stores.
     * 
     * @return all configured CRSs.
     * @throws CRSConfigurationException
     *             if the implementation was confronted by an exception and could not deliver the requested crs. This
     *             exception should not be thrown if no CoordinateSystems were found, in the latter case an empty List (
     *             a list with size == 0 ) should be returned.
     */
    public List<ICRS> getAvailableCRSs() throws CRSConfigurationException {
        List<ICRS> result = new ArrayList<ICRS>();
        for (CRSStore store : getAll()) {
            result.addAll(store.getAvailableCRSs());
        }
        return result;
    }

    /**
     * This methods allows to get all available identifiers and not in the coordinatesystems themselves from all stores.
     * 
     * @return the identifiers of all configured CRSs.
     * @throws CRSConfigurationException
     *             if the implementation was confronted by an exception and could not deliver the requested crs. This
     *             exception should not be thrown if no CoordinateSystems were found, in the latter case an empty List (
     *             a list with size == 0 ) should be returned.
     */
    public List<CRSCodeType[]> getAvailableCRSCodes() throws CRSConfigurationException {
        List<CRSCodeType[]> result = new ArrayList<CRSCodeType[]>();
        for (CRSStore store : getAll()) {
            result.addAll(store.getAvailableCRSCodes());
        }
        return result;

    }

    /***************************************************/

    /**
     * Returns a {@link CRSRef} instance which is not resolved.
     * 
     * @param uri
     *            the uri of the resource which is used to resolve the reference
     * @return an unresolved {@link CRSRef} instance
     */
    public static CRSRef getCRSRef(String uri) {
        return getCRSRef(uri, false);
    }

    /**
     * Return a {@link CRSRef} instance which is not resolved.
     * 
     * @param uri
     *            the uri of the resource which is used to resolve the reference
     * @param forceXY
     *            flag indicating if the axis order should be as configured or xy
     * @return an unresolved {@link CRSRef} instance
     */
    public static CRSRef getCRSRef(String uri, final boolean forceXY) {
        return new CRSRef(new ReferenceResolver() {
            @Override
            public Object getObject(String uri, String baseURL) {
                ICRS crs = null;
                try {
                    crs = lookup(uri, forceXY);
                } catch (UnknownCRSException e) {
                    LOG.debug("Could not find CRS with uri " + uri + "; return null.");
                }
                return crs;
            }
        }, uri, null, forceXY);
    }

    /**
     * Returns a {@link CRSRef} wrapping the given CRS. The uri of the reference is set to the id of the given
     * {@link ICRS}
     * 
     * @param crs
     *            the {@link ICRS} to wrap
     * @return
     */
    public static CRSRef getCRSRef(final ICRS crs) {
        CRSRef newRef = new CRSRef(new ReferenceResolver() {
            @Override
            public Object getObject(String uri, String baseURL) {
                return crs;
            }
        }, crs.getId(), null);
        return newRef;
    }

    /**
     * Creates a direct {@link ICRS} instance from the given name not just a {@link CRSRef}, if no {@link ICRS} was
     * found an {@link UnknownCRSException} will be thrown. All configured {@link CRSStore}s will be considered and the
     * first match returned.
     * 
     * @param name
     *            of the crs, e.g. EPSG:4326
     * @return a direct {@link ICRS} instance corresponding to the given name not just a {@link CRSRef}, using all
     *         configured {@link CRSStore}s.
     * @throws UnknownCRSException
     *             if a {@link ICRS} with the name is not known
     */
    public synchronized static ICRS lookup(String name) throws UnknownCRSException {
        return lookup(name, false);
    }

    /**
     * Creates a direct {@link ICRS} instance from the given name not just a {@link CRSRef}, if no {@link ICRS} was
     * found an {@link UnknownCRSException} will be thrown. All configured {@link CRSStore}s will be considered and the
     * first match returned.
     * 
     * @param name
     *            of the crs, e.g. EPSG:4326
     * @param forceXY
     *            true if the axis order of the coordinate system should be x/y (EAST/NORTH; WEST/SOUTH); false id the
     *            defined axis order should be used
     * @return a direct {@link ICRS} instance corresponding to the given name not just a {@link CRSRef}, using all
     *         configured {@link CRSStore}s.
     * @throws UnknownCRSException
     *             if a {@link ICRS} with the name is not known
     */
    public synchronized static ICRS lookup(String name, boolean forceXY) throws UnknownCRSException {
        return lookup(null, name, forceXY);
    }

    /**
     * Creates a direct {@link ICRS} instance from the given {@link CRSCodeType} not just a {@link CRSRef}, if no
     * {@link ICRS} was found an {@link UnknownCRSException} will be thrown. All configured {@link CRSStore}s will be
     * considered and the first match returned.
     * 
     * @param codeType
     *            the {@link CodeType} of the CRS to return
     * @return a direct {@link ICRS} instance with the given {@link CodeType} not just a {@link CRSRef}
     * @throws UnknownCRSException
     *             if a {@link ICRS} with the name is not known
     */
    public synchronized static ICRS lookup(CRSCodeType codeType) throws UnknownCRSException {
        return lookup(null, codeType);
    }

    /**
     * Creates a direct {@link ICRS} instance from the given name using not just a {@link CRSRef} the given storeID, if
     * no {@link ICRS} was found in the given store an {@link UnknownCRSException} will be thrown.
     * 
     * @param storeId
     *            identifier of the {@link CRSStore} looking for the {@link ICRS} with the given name, may be
     *            <code>null</code> if in all {@link CRSStore}s should be searched
     * @param name
     *            of the crs, e.g. EPSG:31466
     * @return a direct {@link ICRS} instance not just a {@link CRSRef} corresponding to the given name from the
     *         {@link CRSStore} with the given id
     * @throws UnknownCRSException
     *             if a {@link ICRS} with the name is not known
     */
    public synchronized static ICRS lookup(String storeId, String name) throws UnknownCRSException {
        return lookup(storeId, name, false);
    }

    /**
     * Creates a direct {@link ICRS} instance with the given name not just a {@link CRSRef} using the given storeId, if
     * no {@link ICRS} was found an {@link UnknownCRSException} will be thrown.
     * 
     * @param storeIdName
     *            identifier of the store, looking for the {@link ICRS} instance, may be <code>null</code> if in all
     *            {@link CRSStore}s should be searched
     * @param name
     *            of the crs, e.g. EPSG:31466
     * @param forceXY
     *            true if the axis order of the coordinate system should be x/y (EAST/NORTH; WEST/SOUTH); false id the
     *            defined axis order should be used
     * @return a direct {@link ICRS} instance not just a {@link CRSRef} corresponding to the given name from the
     *         {@link CRSStore} with the given id
     * @throws UnknownCRSException
     *             if a {@link ICRS} with the name is not known
     */
    public synchronized static ICRS lookup(String storeIdName, String name, boolean forceXY)
            throws UnknownCRSException {
        CRSStore crsStore = get(storeIdName);
        if (crsStore != null) {
            return lookupStore(crsStore, name, forceXY);
        } else {
            for (String stId : storeIds) {
                CRSStore store = idToCRSStore.get(stId);
                try {
                    ICRS crs = lookupStore(store, name, forceXY);
                    if (crs != null) {
                        return crs;
                    }
                } catch (UnknownCRSException e) {
                    // nothing to do
                }
            }
        }
        throw new UnknownCRSException(name);
    }

    /**
     * Creates a direct {@link ICRS} instance from the given {@link CRSCodeType} notjustjust a {@link CRSRef} using the
     * given storeId, if no {@link ICRS} was found an {@link UnknownCRSException} will be thrown.
     * 
     * @param storeId
     *            identifier of the store, looking for the {@link ICRS} instance, may be <code>null</code> if in all
     *            {@link CRSStore}s should be searched
     * @param crsCodeType
     *            the {@link CRSCodeType} of the ICRS to return
     * @return a real {@link ICRS} not just a reference.
     * @throws UnknownCRSException
     *             if a {@link ICRS} with the name is not known
     */
    public synchronized static ICRS lookup(String storeId, CRSCodeType crsCodeType) throws UnknownCRSException {
        CRSStore crsStore = get(storeId);
        if (crsStore != null) {
            return lookupStore(crsStore, crsCodeType, false);
        } else {
            for (String sId : storeIds) {
                CRSStore store = idToCRSStore.get(sId);
                try {
                    ICRS crs = lookupStore(store, crsCodeType, false);
                    if (crs != null) {
                        return crs;
                    }
                } catch (UnknownCRSException e) {
                    // nothing to do
                }
            }
        }
        throw new UnknownCRSException(crsCodeType.getOriginal());
    }

    /**
     * Creates a {@link ICRS} from the given name using the given {@link CRSStore}, if no {@link ICRS} was found an
     * {@link UnknownCRSException} will be thrown.
     * 
     * @param crsStore
     *            {@link CRSStore} instance, looking for the {@link ICRS} instance, may not be <code>null</code>, if in
     *            all {@link CRSStore}s should be searched
     * @param name
     *            of the crs, e.g. EPSG:31466
     * @param forceXY
     *            true if the axis order of the coordinate system should be x/y (EAST/NORTH; WEST/SOUTH); false id the
     *            defined axis order should be used
     * @throws UnknownCRSException
     *             if name is not known
     * @throws IllegalArgumentException
     *             if crsStore is null
     */
    private static ICRS lookupStore(CRSStore crsStore, String name, boolean forceXY) throws UnknownCRSException {
        if (crsStore == null) {
            throw new IllegalArgumentException(Messages.get("CRSManager.STORE_NULL"));
        }
        long sT = currentTimeMillis();
        long eT = currentTimeMillis() - sT;
        LOG.debug("Getting provider: " + crsStore + " took: " + eT + " ms.");
        ICRS realCRS = null;
        try {
            sT = currentTimeMillis();
            realCRS = crsStore.getCRSByCode(CRSCodeType.valueOf(name), forceXY);
            if (realCRS == null) {
                // TODO: try to get CRS with lower case id (bug with id handling in the abstractStore cache)
                realCRS = crsStore.getCRSByCode(CRSCodeType.valueOf(name.toLowerCase()), forceXY);
            }
            eT = currentTimeMillis() - sT;
            LOG.debug("Getting crs ( " + name + " )from provider: " + crsStore + " took: " + eT + " ms.");
        } catch (CRSConfigurationException e) {
            String msg = Messages.get("CRSManager.BROKEN_CRS_CONFIG", name, e.getMessage());
            LOG.debug(msg, e);
            throw new RuntimeException(msg, e);
        }
        if (realCRS == null) {
            throw new UnknownCRSException(name);
        }
        LOG.debug("Successfully created the crs with id: " + name);
        return realCRS;
    }

    /**
     * Creates a {@link ICRS} from the given name using the given {@link CRSStore}, if no {@link ICRS} was found an
     * {@link UnknownCRSException} will be thrown.
     * 
     * @param crsStore
     *            {@link CRSStore} instance, looking for the {@link ICRS} instance, may not be <code>null</code>, if in
     *            all {@link CRSStore}s should be searched
     * @param crsCodeType
     *            of the crs, e.g. EPSG:31466
     * @param forceXY
     *            true if the axis order of the coordinate system should be x/y (EAST/NORTH; WEST/SOUTH); false id the
     *            defined axis order should be used
     * @throws UnknownCRSException
     *             if name is not known
     * @throws IllegalArgumentException
     *             if crsStore is null
     */
    private static ICRS lookupStore(CRSStore crsStore, CRSCodeType crsCodeType, boolean forceXY)
            throws UnknownCRSException {
        if (crsStore == null) {
            throw new IllegalArgumentException(Messages.get("CRSManager.STORE_NULL"));
        }
        ICRS realCRS = null;
        try {
            realCRS = crsStore.getCRSByCode(crsCodeType);
        } catch (CRSConfigurationException e) {
            LOG.error(e.getMessage(), e);
        }
        if (realCRS == null) {
            throw new UnknownCRSException(crsCodeType.getOriginal());
        }
        LOG.debug("Successfully created the crs with id: " + crsCodeType);
        return realCRS;
    }

    /**
     * @param storeId
     *            identifier of the {@link CRSStore} instance, may be <code>null</code>, then the first transformation
     *            factory will be returned
     * @return the {@link TransformationFactory} instance assigned to the store with the given id or the first one in
     *         the list of {@link TransformationFactory}s if no such instance has been created or <code>null</code> if
     *         no {@link TransformationFactory} instance could be found.
     */
    public static final TransformationFactory getTransformationFactory(String storeId) {
        if (storeId == null) {
            for (TransformationFactory tf : idToTransF.values()) {
                return tf;
            }
        }
        return idToTransF.get(storeId);
    }

    /**
     * Get a {@link Transformation} with given id, or <code>null</code> if it does not exist.
     * 
     * @param storeId
     *            identifier of the store, looking for the {@link Transformation}, may be <code>null</code> if in all
     *            {@link CRSStore}s should be searched
     * @param id
     *            of the {@link Transformation}
     * @return the identified {@link Transformation} or <code>null<code> if no such transformation is found.
     */
    public synchronized static Transformation getTransformation(String storeId, String id) {
        CRSStore crsStore = idToCRSStore.get(storeId);
        if (crsStore == null) {
            for (String sId : storeIds) {
                CRSStore store = idToCRSStore.get(sId);
                Transformation transformation = getTransformation(store, id);
                if (transformation != null) {
                    return transformation;
                }
            }
        } else {
            return getTransformation(crsStore, id);
        }
        return null;
    }

    /**
     * Retrieve a {@link Transformation} (chain) which transforms coordinates from the given source into the given
     * target crs. If no such {@link Transformation} could be found or the implementation does not support inverse
     * lookup of transformations <code>null<code> will be returned.
     * 
     * @param storeId
     *            identifier of the store, looking for the {@link Transformation}, may be <code>null</code> if in all
     *            {@link CRSStore}s should be searched
     * @param sourceCRS
     *            start {@link ICRS} of the transformation (chain)
     * @param targetCRS
     *            end {@link ICRS} of the transformation (chain).
     * @return the given {@link Transformation} or <code>null<code> if no such transformation was found.
     * @throws TransformationException
     * @throws IllegalArgumentException
     */
    public synchronized static Transformation getTransformation(String storeId, ICRS sourceCRS, ICRS targetCRS)
            throws IllegalArgumentException, TransformationException {
        return getTransformation(storeId, sourceCRS, targetCRS, null);
    }

    /**
     * Retrieve a {@link Transformation} (chain) which transforms coordinates from the given source into the given
     * target crs. If no such {@link Transformation} could be found or the implementation does not support inverse
     * lookup of transformations <code>null<code> will be returned.
     * 
     * @param storeId
     *            identifier of the store, looking for the {@link Transformation}, may be <code>null</code> if in all
     *            {@link CRSStore}s should be searched
     * @param sourceCRS
     *            start {@link ICRS} of the transformation (chain)
     * @param targetCRS
     *            end {@link ICRS} of the transformation (chain).
     * @param transformationsToBeUsed
     *            a list of transformations which must be used on the resulting transformation chain, may be
     *            <code>null</code> or empty
     * @return the given {@link Transformation} or <code>null<code> if no such transformation was found.
     * @throws TransformationException
     * @throws IllegalArgumentException
     */
    public synchronized static Transformation getTransformation(String storeId, ICRS sourceCRS, ICRS targetCRS,
            List<Transformation> transformationsToBeUsed) throws IllegalArgumentException, TransformationException {
        if (storeId != null) {
            TransformationFactory fac = getTransformationFactory(storeId);
            return fac.createFromCoordinateSystems(sourceCRS, targetCRS, transformationsToBeUsed);
        } else {
            for (TransformationFactory tf : idToTransF.values()) {
                Transformation trans = tf.createFromCoordinateSystems(sourceCRS, targetCRS,
                        transformationsToBeUsed);
                if (trans != null) {
                    return trans;
                }
            }
        }
        return null;
    }

    /**
     * Get a {@link Transformation} with given id, or <code>null</code> if it does not exist.
     * 
     * @param crsStore
     *            {@link CRSStore} instance, looking for the {@link Transformation}, may not be <code>null</code>
     * @param id
     *            of the {@link Transformation}
     * @return the identified {@link Transformation} or <code>null<code> if no transformation is found.
     * @throws IllegalArgumentException
     *             if crsStore is null
     */
    private synchronized static Transformation getTransformation(CRSStore crsStore, String id) {
        if (crsStore == null) {
            throw new IllegalArgumentException(Messages.get("CRSManager.STORE_NULL"));
        }
        CRSResource t = null;
        try {
            t = crsStore.getDirectTransformation(id);
        } catch (Throwable e) {
            LOG.debug("Could not retrieve a transformation for id: " + id);
        }
        if (t != null) {
            return (Transformation) t;
        }
        LOG.debug("The given id: " + id + " is not of type transformation return null.");
        return null;
    }

    protected void remove(String id) {
        if (id != null) {
            storeIds.remove(id);
            idToCRSStore.remove(id);
            idToTransF.remove(id);
        }
    }

}