Package org.locationtech.udig.project.internal

Source Code of org.locationtech.udig.project.internal.CatalogRef$LayerCatalogRefPersister

/* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2004-2012, Refractions Research Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
* License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
*/
package org.locationtech.udig.project.internal;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.locationtech.udig.catalog.CatalogPlugin;
import org.locationtech.udig.catalog.ICatalog;
import org.locationtech.udig.catalog.ID;
import org.locationtech.udig.catalog.IGeoResource;
import org.locationtech.udig.catalog.IService;
import org.locationtech.udig.catalog.IServiceFactory;
import org.locationtech.udig.catalog.ServiceParameterPersister;
import org.locationtech.udig.catalog.URLUtils;
import org.locationtech.udig.project.internal.impl.LayerImpl;
import org.locationtech.udig.ui.ProgressManager;

import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IExportedPreferences;
import org.eclipse.core.runtime.preferences.IPreferencesService;
import org.eclipse.emf.ecore.resource.Resource;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;

/**
* This class is an EMF bridge for persisting the connection parameters required to add the services
* required by the layer into the catalog if the catalog does not have those services. For example
* if a map is moved to another uDig install.
* <p>
* One important point to remember is that a layer can be associated with many resources and
* therefore may have to store the parameters for many services
* </p>
* <p>
* The {@link #marshalConnectionParameters()} method is used to persist the connection parameters
* and the {@link #parseResourceParameters(String)} is used to provide the CatalogRef with the
* connection parameters again. However, they services are not loaded until load is called. This is
* so that uDig doesn't block until it is a good time to block. (IE when getGeoResources is called).
* </p>
*
* @author Jesse
* @since 1.0.0
*/
public class CatalogRef {
    protected Layer layer;

    protected static final String ENCODING = ProjectPlugin.Implementation.ENCODING;

    private static final String ERROR_SAVING = "Error saving"; //$NON-NLS-1$

    protected Map<ID, Map<String, Serializable>> connectionParams = Collections
            .synchronizedMap(new HashMap<ID, Map<String, Serializable>>());

    private volatile boolean loaded = false;

    /**
     * Construct <code>LayerRef</code>.
     *
     * @param layer
     */
    public CatalogRef( Layer layer ) {
        this.layer = layer;
    }

    /**
     * Construct <code>LayerRef</code>.
     */
    public CatalogRef() {
        // do nothing
    }

    /**
     * Marshals the connection parameters into an XML safe string. IE as string that can be embedded
     * in an XML document.
     * <p>
     * Basic process is to use the {@link ServiceParameterPersister} class to persist the
     * parameters. It writes to a Preferences object so a node is obtained from the
     * PreferenceServices and the parameters are written to that node which is then exported to a
     * string. The string is encoded by URLEncoder (so it can go in an XML doc) and the string is
     * returned
     * </p>
     */
    public String marshalConnectionParameters() {
        try {
            List<IGeoResource> resources = layer.getGeoResources();
            Preferences toSave = Platform.getPreferencesService().getRootNode().node(
                    ProjectPlugin.ID).node(layerIDToString());
            if (resources != LayerImpl.NULL) {
                connectionParams.clear();

                ServiceParameterPersister persister = new LayerCatalogRefPersister(
                        connectionParams, getMapFile());

                persister.store(ProgressManager.instance().get(), toSave, resources);
            }

            ByteArrayOutputStream out = new ByteArrayOutputStream();
            Platform.getPreferencesService().exportPreferences((IEclipsePreferences) toSave, out,
                    null);
            toSave.clear();

            return URLEncoder.encode(out.toString(), ENCODING);
        } catch (Throwable t) {
            ProjectPlugin
                    .log(
                            "Error saving parameters for layer: " + (layer == null ? "unkown id" : layer.getID()) + " this map cannot be sent to a collegue", t); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
            return ERROR_SAVING;
        }
    }

    /**
     * Create a File object that is the map file. This is used so that the File URLs can be saved
     * relative to the map file
     */
    private File getMapFile() {
        Resource resource = layer.getMapInternal().eResource();
        if (resource != null) {
            return new File(resource.getURI().toFileString());
        } else {
            return null;
        }
    }

    private String layerIDToString() throws UnsupportedEncodingException {
        if (layer != null && layer.getID() != null)
            return URLEncoder.encode(layer.getID().toString(), ENCODING);
        else
            return "ID_" + System.currentTimeMillis(); //$NON-NLS-1$
    }

    /**
     * Reloads the parameters for the resource's parameters from the string and stores them. Services
     * aren't created until load() is called.
     *
     * @param string
     */
    public void parseResourceParameters( String string ) {
        if (string == null || string.length() == 0) {
            return;
        }
        String decoded;
        try {
            decoded = URLDecoder.decode(string, ENCODING);
        } catch (UnsupportedEncodingException e) {
            decoded = string;
        }

        ByteArrayInputStream input = new ByteArrayInputStream(decoded.getBytes());
        try {
            IPreferencesService preferencesService = Platform.getPreferencesService();
            IExportedPreferences paramsNode = preferencesService.readPreferences(input);

            ServiceParameterPersister persister = new LayerCatalogRefPersister(connectionParams,
                    null);

            persister.restore(findParameterNode(paramsNode));
        } catch (Throwable e) {
            // ok maybe it is an from an older version of uDig so try the oldCatalogRef
            ProjectPlugin.log("CatalogRef#parseResourceParameters, couldn't load paramters", e); //$NON-NLS-1$
        }

    }

    private Preferences findParameterNode( IExportedPreferences paramsNode )
            throws BackingStoreException {
        String[] name = paramsNode.childrenNames();

        Preferences plugin = paramsNode.node(name[0]);
        name = plugin.childrenNames();

        return plugin.node(name[0]);
    }

    /**
     * @return Returns the layer.
     * @uml.property name="layer"
     */
    public Layer getLayer() {
        return layer;
    }

    /**
     * @param layer The layer to set.
     * @uml.property name="layer"
     */
    public void setLayer( Layer layer ) {
        this.layer = layer;
    }

    /**
     * Adds the required services into the catalog.
     */
    public void load() {

        ICatalog catalog = CatalogPlugin.getDefault().getLocalCatalog();
        IServiceFactory serviceFactory = CatalogPlugin.getDefault().getServiceFactory();
        synchronized (connectionParams) {
            for( Map.Entry<ID, Map<String, Serializable>> entry : connectionParams.entrySet() ) {
                Map<String, Serializable> params = entry.getValue();
                boolean couldResolve = resolveURLs(params);
                if (!couldResolve) {
                    // if couldn't resolve log the warning and continue. There is no way we can load
                    // the resource
                    ProjectPlugin
                            .log(
                                    "Warning: couldn't find the layer's resources in the catalog and can't construct it because the current map" //$NON-NLS-1$
                                            + " does not have a Resource:" + layer.getID(), //$NON-NLS-1$
                                    new Exception("Failure loading layer")); //$NON-NLS-1$
                    continue;
                }

                List<IService> createdServices = serviceFactory.createService(params);
                updateLayerID(createdServices);
                if (serviceExistsInCatalog(createdServices, catalog)) {
                    continue;
                } else {
                    addServicesToCatalog(createdServices, catalog);
                }

            }
        }
        loaded = true;
    }

   
    /**
     * This is confusing so read on...
     * <p>
     * Because a Service's id is determined by where the resource is located (for shapefile it is
     * the file of the .shp). The id of the service could be different from the last time the layer
     * was loaded (the shp file moved). Or if this layer is added to a different uDig the shp file
     * may be in a different location than on the previous system.
     * </p>
     * <p>
     * Because of this the ids services must be inspected and layer's id must be checked to ensure
     * it is still valid. If not it must be updated to refer to a valid service
     * </p>
     *
     * @param createdServices
     */
    private void updateLayerID( List<IService> createdServices ) {
        if (createdServices.isEmpty()) {
            return;
        }

        URL layerid = getLayer().getID();
        for( IService service : createdServices ) {
            if (URLUtils.urlEquals(layerid, service.getIdentifier(), true)) {
                return;
            }
        }

        // layer id is bad :( create a new one from the 1st service.
        URL id = createdServices.get(0).getIdentifier();
        String serviceIdAsString = URLUtils.urlToString(id, false);
        String externalizedLayerID = layerid.toExternalForm();

        String[] segments = externalizedLayerID.split("#"); //$NON-NLS-1$

        if (segments.length < 2) {
            // the service that this layer was originally created from does not obey the
            // convention where georesources must be the serviceid#subid
            ProjectPlugin
                    .log("Some service doesn't obey the convention where georesources must be the serviceid#subid. \n\nLayer id = " //$NON-NLS-1$
                            + layerid + "\n\nThe potential culprits are: " + createdServices); //$NON-NLS-1$
            return;
        }

        try {
            layer.setID(new URL(serviceIdAsString + "#" + segments[1])); //$NON-NLS-1$
        } catch (MalformedURLException e) {
            throw (RuntimeException) new RuntimeException( ).initCause( e );
        }

    }
   
    private void addServicesToCatalog( List<IService> createdServices, ICatalog catalog ) {
        for( IService service : createdServices ) {
            catalog.add(service);
        }
    }

    private boolean serviceExistsInCatalog( List<IService> createdServices, ICatalog catalog ) {
        boolean found = false;
        for( IService service : createdServices ) {
            if (catalog.getById(IService.class, service.getID(), ProgressManager.instance()
                    .get()) != null) {
                found = true;
            } else {
                catalog.add(service);
            }
        }
        return found;
    }

    /**
     * Takes the params and tries to change them so if they are relative URLs then they are relative
     * to the map.
     *
     * @return Return true if the map has a resource and therefore a URI false if resolving failed.
     *         This may happen if the map was copied and no longer has a URL. In this case just
     *         search the catalog and don't load resources from the given URI.
     */
    private boolean resolveURLs( Map<String, Serializable> params ) {
        Set<Entry<String, Serializable>> entries = params.entrySet();
        for( Entry<String, Serializable> entry : entries ) {
            if (entry.getValue() instanceof URL) {
                String url = ((URL) entry.getValue()).toExternalForm();
                try {
                    File mapFile = getMapFile();
                    if (mapFile == null) {
                        return false;
                    }
                    entry.setValue(URLUtils.constructURL(mapFile, url));
                } catch (MalformedURLException e) {
                    // oh well we'll keep see what happens, maybe this isn't possible
                    ProjectPlugin.log("Couldn't resolve the URL", e); //$NON-NLS-1$
                }
            }
        }
        return true;

    }

    /**
     * Returns true if the required GeoResources have been added to the catalog.
     *
     * @return
     * @uml.property name="loaded"
     */
    public boolean isLoaded() {
        return loaded;
    }

    /**
     * Used to store a catalog reference into a project file.
     * <p>
     * Please note that catalog references here are stored as connectionParameters (rather than a
     * simple ID). This allows a project file to find and locate services when loaded up in another
     * copy of uDig.
     */
    private static class LayerCatalogRefPersister extends ServiceParameterPersister {

        private Map<ID, Map<String, Serializable>> allParams;

        public LayerCatalogRefPersister( Map<ID, Map<String, Serializable>> allParams, File mapFile ) {
            super(CatalogPlugin.getDefault().getLocalCatalog(), CatalogPlugin.getDefault()
                    .getServiceFactory(), mapFile);
            this.allParams = allParams;
        }

        @Override
        protected void locateService( ID id, Map<String, Serializable> map, Map<String,Serializable> properties, Map<ID, Map<String, Serializable>> resourcePropertyMap  ) {
            if (allParams.containsKey(id))
                ProjectPlugin
                        .log("LayerCatalogRefPersister#locateService: duplicate resource ids when loading paramers"); //$NON-NLS-1$

            allParams.put(id, map);
        }

    }
}
TOP

Related Classes of org.locationtech.udig.project.internal.CatalogRef$LayerCatalogRefPersister

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.