Package org.locationtech.udig.catalog.internal

Source Code of org.locationtech.udig.catalog.internal.CatalogImpl

/*
*    uDig - User Friendly Desktop Internet GIS client
*    http://udig.refractions.net
*    (C) 2004-2011, 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.catalog.internal;

import static org.locationtech.udig.catalog.IResolve.Status.CONNECTED;
import static org.locationtech.udig.catalog.IResolve.Status.NOTCONNECTED;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import org.locationtech.udig.catalog.CatalogPlugin;
import org.locationtech.udig.catalog.ICatalog;
import org.locationtech.udig.catalog.ICatalogInfo;
import org.locationtech.udig.catalog.ID;
import org.locationtech.udig.catalog.IGeoResource;
import org.locationtech.udig.catalog.IGeoResourceInfo;
import org.locationtech.udig.catalog.IResolve;
import org.locationtech.udig.catalog.IResolveChangeEvent;
import org.locationtech.udig.catalog.IResolveChangeListener;
import org.locationtech.udig.catalog.IResolveDelta;
import org.locationtech.udig.catalog.IService;
import org.locationtech.udig.catalog.IServiceFactory;
import org.locationtech.udig.catalog.IServiceInfo;
import org.locationtech.udig.catalog.ServiceParameterPersister;
import org.locationtech.udig.catalog.TemporaryResourceFactory;
import org.locationtech.udig.catalog.URLUtils;
import org.locationtech.udig.catalog.interceptor.GeoResourceInterceptor;
import org.locationtech.udig.catalog.interceptor.ServiceInterceptor;
import org.locationtech.udig.catalog.moved.MovedService;
import org.locationtech.udig.catalog.util.AST;
import org.locationtech.udig.catalog.util.ASTFactory;
import org.locationtech.udig.catalog.util.IFriend;
import org.locationtech.udig.core.WeakHashSet;
import org.locationtech.udig.core.internal.ExtensionPointList;
import org.locationtech.udig.core.internal.ExtensionPointProcessor;
import org.locationtech.udig.core.internal.ExtensionPointUtil;
import org.locationtech.udig.ui.PlatformGIS;

import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IExportedPreferences;
import org.eclipse.core.runtime.preferences.IPreferencesService;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.widgets.Display;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;

import com.vividsolutions.jts.geom.Envelope;

/**
* Local Catalog implementation (tracking known services, persisted by XML currently).
*
* @author David Zwiers (Refractions Research)
* @author Jody Garnett
* @since 0.6
* @version 1.2
*/
public class CatalogImpl extends ICatalog {
    private static final String TEMPORARY_RESOURCE_EXT_ID = "org.locationtech.udig.catalog.temporaryResource"; //$NON-NLS-1$

    /** All services known to the local catalog */
    private final Set<IService> services = new CopyOnWriteArraySet<IService>();
    /** Information about this catalog */
    private ICatalogInfo metadata;

    private final Set<IResolveChangeListener> catalogListeners;

    /** @see getTemporaryDescriptorClasses */
    private String[] descriptors;

    public CatalogImpl() {
        CatalogInfoImpl metadata = new CatalogInfoImpl();
        metadata.setTitle(Messages.CatalogImpl_localCatalog_title);
        try {
            metadata.setSource(new URL("http://localhost")); //$NON-NLS-1$
        } catch (MalformedURLException e) {
            // do nothing
        }

        this.metadata = metadata;
        catalogListeners = Collections.synchronizedSet(new WeakHashSet<IResolveChangeListener>());
    }

    public CatalogImpl( ICatalogInfo metadata ) {
        this();
        this.metadata = metadata;
    }

    /**
     * @see org.locationtech.udig.catalog.ICatalog#addCatalogListener(org.locationtech.udig.catalog.ICatalog.ICatalogListener)
     * @param listener
     */
    public void addCatalogListener( IResolveChangeListener listener ) {
        catalogListeners.add(listener);
    }
    public void addListener( IResolveChangeListener listener ) {
        catalogListeners.add(listener);
    }

    /**
     * @see org.locationtech.udig.catalog.ICatalog#removeCatalogListener(org.locationtech.udig.catalog.ICatalog.ICatalogListener)
     * @param listener
     */
    public void removeCatalogListener( IResolveChangeListener listener ) {
        catalogListeners.remove(listener);
    }
    public void removeListener( IResolveChangeListener listener ) {
        catalogListeners.remove(listener);
    }

    public IService add( IService service ) throws UnsupportedOperationException {
        if (service == null || service.getIdentifier() == null)
            throw new NullPointerException("Cannot have a null id"); //$NON-NLS-1$
        ID id = service.getID();

        IService found = getById(IService.class, id, new NullProgressMonitor());

        if (found != null) {
            // clean up the service that was passed in
            try {
                service.dispose(new NullProgressMonitor());
            } catch (Throwable t) {
                CatalogPlugin.trace("dispose " + id, t);
            }
            return found;
        }

        services.add(service);
        runInterceptor(service, ServiceInterceptor.ADDED_ID);
       
        IResolveDelta deltaAdded = new ResolveDelta(service, IResolveDelta.Kind.ADDED);
        IResolveDelta deltaChanged = new ResolveDelta(this, Collections.singletonList(deltaAdded));
        fire(new ResolveChangeEvent(CatalogImpl.this, IResolveChangeEvent.Type.POST_CHANGE,deltaChanged));
       
        return service;
    }

    /**
     * @see org.locationtech.udig.catalog.ICatalog#remove(org.locationtech.udig.catalog.IService)
     * @param entry
     * @throws UnsupportedOperationException
     */
    public void remove( IService entry ) throws UnsupportedOperationException {
        if (entry == null || entry.getIdentifier() == null){
            throw new NullPointerException("Cannot have a null id"); //$NON-NLS-1$
        }
        IResolveDelta deltaRemoved = new ResolveDelta(entry, IResolveDelta.Kind.REMOVED);
        IResolveDelta deltaChanged = new ResolveDelta(this, Collections.singletonList(deltaRemoved));
        fire(new ResolveChangeEvent(CatalogImpl.this, IResolveChangeEvent.Type.PRE_DELETE,
                deltaChanged));
       
        services.remove(entry);
        runInterceptor(entry, ServiceInterceptor.REMOVED_ID);
       
        fire(new ResolveChangeEvent(CatalogImpl.this, IResolveChangeEvent.Type.POST_CHANGE,
                deltaRemoved));
    }
    public void replace( ID id, IService replacement ) throws UnsupportedOperationException {
        if (replacement == null || replacement.getIdentifier() == null || id == null) {
            throw new NullPointerException("Cannot have a null id"); //$NON-NLS-1$
        }
        final IService service = getServiceById(id);
        List<IResolveDelta> changes = new ArrayList<IResolveDelta>();
        List<IResolveDelta> childChanges = new ArrayList<IResolveDelta>();
        try {
            List< ? extends IGeoResource> newChildren = replacement.resources(null);
            List< ? extends IGeoResource> oldChildren = service.resources(null);
            if (oldChildren != null)
                for( IGeoResource oldChild : oldChildren ) {
                    String oldName = oldChild.getIdentifier().toString();

                    for( IGeoResource child : newChildren ) {
                        String name = child.getIdentifier().toString();
                        if (oldName.equals(name)) {
                            childChanges.add(new ResolveDelta(child, oldChild,
                                    IResolveDelta.NO_CHILDREN));
                            break;
                        }
                    }
                }
        } catch (IOException ignore) {
            // no children? Not a very good entry ..
        }
        changes.add(new ResolveDelta(service, replacement, childChanges));

        IResolveDelta deltas = new ResolveDelta(this, changes);
        IResolveChangeEvent event = new ResolveChangeEvent(this,
                IResolveChangeEvent.Type.PRE_DELETE, deltas);
        fire(event);
        services.remove(service);
        runInterceptor(service, ServiceInterceptor.REMOVED_ID);

        PlatformGIS.run(new IRunnableWithProgress(){

            public void run( IProgressMonitor monitor ) throws InvocationTargetException,
                    InterruptedException {
                try {
                    service.dispose(monitor);
                } catch (Throwable e) {
                    CatalogPlugin.log("error disposing of: " + service.getIdentifier(), e); //$NON-NLS-1$
                }
            }

        });

        services.add(replacement);
        runInterceptor(replacement, ServiceInterceptor.ADDED_ID);
        event = new ResolveChangeEvent(this, IResolveChangeEvent.Type.POST_CHANGE, deltas);

        if (!id.equals(replacement.getIdentifier())) {
            // the service has actually moved
            IService moved = new MovedService(id, replacement.getID());
            services.add(moved);
            runInterceptor(moved, ServiceInterceptor.ADDED_ID);
        }
        fire(event);
    }
    //
    // Registration: creation
    //
   
    /**
     * This is the preferred way to connect to a service using a URL.
     * <p>
     * If the service is already in the catalog it will be returned; if not we will
     * connect to the service (add it to the catalog for safekeeping and cleanup)
     * and return you the result.
     * <p>
     * The catalog takes responsibility for cleaning up after the service (ie call dispose())
     * so you are free to continue with your work.
     * @param url
     * @param monitor
     * @return Service used to access the provided url
     */
    public IService acquire( URL url, IProgressMonitor monitor ) throws IOException {
        List<IService> possible = new ArrayList<IService>();
        IService createdService = null;
       
        if (monitor == null)
            monitor = new NullProgressMonitor();

        monitor.beginTask("acquire", 100);
        monitor.subTask("acquire services");

        monitor.worked(10);
        try {

            possible = constructServices(url, monitor);

            if (possible.isEmpty()) {
                throw new IOException("Unable to connect to any service supporting " + url);
            }
           
            createdService = possible.get(0);
           
            try {
               
                add(createdService);// TODO don't clean this one up!
                return createdService;
            } catch (Throwable t) {
                // usually indicates an IOException as the service is unable to connect
                CatalogPlugin.trace("trouble connecting to " + createdService.getID(), t);
            }

        } finally {
            List<IService> members = checkMembers(possible);
           
            for( Iterator<IService> iterator = members.iterator(); iterator.hasNext(); ) {
                IService service = iterator.next();

                if (service.equals(createdService))
                    continue;

                service.dispose(new SubProgressMonitor(monitor, 10));
            }
            monitor.done();
        }
        return null; // unable to connect
    }

    /**
     * This is the preferred way to connect to a service using connection parameters.
     * <p>
     * If the service is already in the catalog it will be returned; if not we will
     * connect to the service (add it to the catalog for safekeeping and cleanup)
     * and return you the result.
     * <p>
     * The catalog takes responsibility for cleaning up after the service (ie call dispose())
     * so you are free to continue with your work.
     * @param connectionParameters
     * @param monitor
     * @return Service used to access the provided connectionParameters
     */
    public IService acquire( Map<String, Serializable> connectionParameters,
            IProgressMonitor monitor ) throws IOException {
       
        List<IService> possible = new ArrayList<IService>();
        IService createdService = null;
       
        if (monitor == null) monitor = new NullProgressMonitor();

        monitor.beginTask("acquire", 100);
        monitor.subTask("acquire services");

        try {

            possible = constructServices(connectionParameters, monitor);

            if (possible.isEmpty()) {
                throw new IOException("Unable to connect to any service ");
            }

            createdService = possible.get(0);

            try {
                add(createdService);// TODO don't clean this one up!
                return createdService;
            } catch (Throwable t) {
                // usually indicates an IOException as the service is unable to connect
                CatalogPlugin.trace("trouble connecting to " + createdService.getID(), t);
            }

        } finally {
            List<IService> members = checkMembers(possible);
           
            for( Iterator<IService> iterator = members.iterator(); iterator.hasNext(); ) {
                IService service = iterator.next();

                if (service.equals(createdService))
                    continue;

                service.dispose(new SubProgressMonitor(monitor, 10));
            }
           
            monitor.done();
        }
        return null;
    }

    //
    // Search Implementations
    //
    public List<IResolve> find( ID id, IProgressMonitor monitor ) {
        URL query = id.toURL();
        Set<IResolve> found = new LinkedHashSet<IResolve>();
       
        //ID id1 = new ID(query);

        // first pass 1.1- use urlEquals on CONNECTED service for subset check
        for( IService service : services ) {
            if (service.getStatus() != CONNECTED)
                continue; // skip non connected service
           
            URL identifier = service.getIdentifier();
            boolean matchParentURL = URLUtils.urlEquals(query, identifier, true);
            boolean matchParentID = service.getID().equals( id, true );

            if (matchParentURL || matchParentID) {
                if (matchedService(query, identifier)) {
                    found.add(service);
                    found.addAll(friends(service));
                } else {
                    IResolve res = getChildById(service, id, true, monitor);
                    if (res != null) {
                        found.add(res);
                        found.addAll(friends(res));
                    }
                }
            }
        }
        // first pass 1.2 - use urlEquals on unCONNECTED service for subset check
        for( IService service : services ) {
            if (service.getStatus() == CONNECTED)
                continue; // already checked in pass 1.1
            URL identifier = service.getIdentifier();
            if (URLUtils.urlEquals(query, identifier, true)) {
                if (service.getStatus() != NOTCONNECTED)
                    continue; // look into not connected service that "match"
                if (matchedService(query, identifier)) {
                    found.add(service);
                    found.addAll(friends(service));
                } else {
                    IResolve res = getChildById(service, id, true, monitor);
                    if (res != null) {
                        found.add(res);
                        found.addAll(friends(res));
                    }
                }
            }
        }
        // first pass 1.3 - use urlEquals on BROKEN or RESTRICTED_ACCESS service for subset check
        // the hope here is that a "friend" will still have data! May be tough for friends
        // to negotiate a match w/ a broken services - but there is still hope...
        for( IService service : services ) {
            if (service.getStatus() == CONNECTED || service.getStatus() == NOTCONNECTED) {
                continue; // already checked in pass 1.1-1.2
            }
            URL identifier = service.getIdentifier();
            if (URLUtils.urlEquals(query, identifier, true)) {
                if (matchedService(query, identifier)) {
                    found.add(service);
                    found.addAll(friends(service));
                } else {
                    IResolve res = getChildById(service, id, true, monitor);
                    if (res != null) {
                        found.add(res);
                        found.addAll(friends(res));
                    }
                }
            }
        }
        // second pass - deep check for georesource on connected services
        // these are resources that dont match the server#membername pattern
        //
        // This code removed for udig 1.1 release
        /*
         * if( found.isEmpty() && query.getRef()!=null ){ // we have not found anything; and we are
         * looking for a georesource // search connected resources ... for( IService service :
         * services ) { if( service.getStatus() == CONNECTED ){ IResolve res = getChildById(service,
         * query, monitor); if( res!=null ){ found.add(res); found.addAll( friends( res)); break; }
         * } } }
         */
        // thirdpass - deep check for georesource on unconnected services
        //
        // This code removed for udig 1.1 release
        /*
         * if( found.isEmpty() && query.getRef()!=null ){ if( false ){
         * CatalogPlugin.log("Warning Deep search in catalog is occurring this is VERY expensive",
         * new Exception("JUST A WARNING")); //$NON-NLS-1$ //$NON-NLS-2$ } // we have not found
         * anything; and we are looking for a georesource // search not connected resources ... for(
         * IService service : services ) { if( service.getStatus() == NOTCONNECTED ){ IResolve res =
         * getChildById(service, query, monitor); if( res!=null ){ found.add(res); found.addAll(
         * friends( res)); break; } } } }
         */
        return new ArrayList<IResolve>(found);
    }

    /**
     * Quick search by url match.
     *
     * @param query
     * @see org.locationtech.udig.catalog.ICatalog#search(org.opengis.filter.Filter)
     * @return List<IResolve>
     * @throws IOException
     */
    public List<IResolve> find( URL query, IProgressMonitor monitor ) {
        return find(new ID(query), monitor);
    }

    /**
     * Check if the provided query is a child of identifier.
     *
     * @param query
     * @param identifier
     * @return true if query may be a child of identifier
     */
    private boolean matchedService( URL query, URL identifier ) {
        return query.getRef() == null && URLUtils.urlEquals(query, identifier, false);
    }

    /**
     * Returns a list of friendly resources working with related data.
     * <p>
     * This method is used internally to determine resource handles that offer different entry
     * points to the same information.
     * </p>
     * A friend can be found via:
     * <ul>
     * <li>Making use of a CSW2.0 association
     * <li>URL Pattern matching for well known cases like GeoServer and MapServer
     * <li>Service Metadata, for example WMS resourceURL referencing a WFS SimpleFeatureType
     * </ul>
     * All of these handles will be returned from the find( URL, monitor ) method. </ul>
     *
     * @param handle
     * @return List of frends, possibly empty
     */
    public List<IResolve> friends( final IResolve handle ) {
        final List<IResolve> friends = new ArrayList<IResolve>();
        ExtensionPointUtil.process(CatalogPlugin.getDefault(),
                "org.locationtech.udig.catalog.friendly", new ExtensionPointProcessor(){ //$NON-NLS-1$
                    /**
                     * Lets find our friends.
                     */
                    public void process( IExtension extension, IConfigurationElement element )
                            throws Exception {
                        try {
                            String target = element.getAttribute("target"); //$NON-NLS-1$
                            String contain = element.getAttribute("contain"); //$NON-NLS-1$
                            if (target != null) {
                                // perform target check
                                if (target.equals(target.getClass().toString())) {
                                    return;
                                }
                            }
                            if (contain != null) {
                                String uri = handle.getIdentifier().toExternalForm();
                                if (!uri.contains(contain)) {
                                    return;
                                }
                            }

                            IFriend friendly = (IFriend) element.createExecutableExtension("class"); //$NON-NLS-1$
                            friends.addAll(friendly.friendly(handle, null));
                        } catch (Throwable t) {
                            CatalogPlugin.log(t.getLocalizedMessage(), t);
                        }
                    }
                });
        return friends;
    }

    /**
     * Utility method to quick hunt for matching service.
     * <p>
     * We are depending on the provided ID being unique for this catalog; please note that in the
     * event the ID locates an IForward instances we will find and return the replacement.
     * <p>
     *
     * @param id Identification of service to find
     * @return Found service handle or null
     */
    private IService getServiceById( final ID id ) {
        if (id == null)
            return null;
        for( IService service : services ) {
            if (id.equals(service.getID())) {
                return service;
            }
        }
        return null;
    }

    public <T extends IResolve> T getById( Class<T> type, final ID id, IProgressMonitor monitor ) {

        IProgressMonitor monitor2 = monitor;;
        if (monitor2 == null)
            monitor2 = new NullProgressMonitor();
        if (id == null)
            return null;

        if (IService.class.isAssignableFrom(type)) {
            monitor2.beginTask(Messages.CatalogImpl_monitorTask, 1);
            IService service = getServiceById(id);
            monitor2.done();
            return type.cast(service);
        }

        URL url = id.toURL();
        if (IResolve.class.isAssignableFrom(type)) {
            for( IService service : services ) {
                if (URLUtils.urlEquals(url, service.getIdentifier(), true)) {
                    IResolve child = getChildById(service, id, false, monitor2);
                    if (child != null)
                        return type.cast(child);
                }
            }
        }
        return null;
    }

    // @Override
    // public <T extends IResolve> T getById(Class<T> type, final URL id, IProgressMonitor monitor)
    // {
    // return getById(type, new ID(id), monitor);
    // }

    /**
     * Utility method that will search in the provided handle for an ID match; especially good for
     * nested content such as folders or WMS layers.
     * <p>
     * <h4>Old Comment</h4> The following comment was original included in the source code: we are
     * not sure it should be believed ... we will do our best to search CONNECTED services first,
     * nut NOTCONNECTED is included in our search. <quote> Although the following is a 'blocking'
     * call, we have deemed it safe based on the following reasons:
     * <ul>
     * <li>This will only be called for Identifiers which are well known.
     * <li>The Services being checked have already been screened, and only a limited number of
     * services (usually 1) will be called.
     * <ol>
     * <li>The Id was acquired from the catalog ... and this is a look-up, in which case the uri
     * exists.
     * <li>The Id was persisted.
     * </ol>
     * </ul>
     * In the future this will also be free, as we plan on caching the equivalent of a
     * getCapabilities document between runs (will have to be updated too as the app has time).
     * </quote> Repeat the following comment is out of date since people are using this method to
     * look for entries that have not been added to the catalog yet.
     *
     * @param roughMatch an ID consists of a URL and other info like a typeQualifier if roughMatch
     *        is true then the extra information is ignored during search
     */
    public IResolve getChildById( IResolve handle, final ID id, boolean roughMatch,
            IProgressMonitor monitor ) {
        IProgressMonitor monitor2 = monitor;
        if (monitor2 == null)
            monitor2 = new NullProgressMonitor();

        if (roughMatch) {
            URL url1 = id.toURL();
            URL url2 = handle.getIdentifier();
            if (new ID(url1).equals(new ID(url2))) {
                return handle;
            }
        } else {
            if (id.equals(handle.getID())) {
                return handle;
            }
        }
        try {
            List< ? extends IResolve> children = handle.members(monitor2);
            if (children == null || children.isEmpty())
                return null;

            monitor2.beginTask(Messages.CatalogImpl_monitorTask2, children.size());
            for( IResolve child : children ) {
                IResolve found = getChildById(child, id, roughMatch, null);
                if (found != null)
                    return found;
            }
        } catch (IOException e) {
            CatalogPlugin.log("Could not search children of " + handle.getIdentifier(), e); //$NON-NLS-1$
        }
        return null;
    }

    /**
     * Performs a search on this catalog based on the specified inputs. The pattern uses the
     * following conventions: use " " to surround a phase use + to represent 'AND' use - to
     * represent 'OR' use ! to represent 'NOT' use ( ) to designate scope The bbox provided shall be
     * in Lat - Long, or null if the search is not to be contained within a specified area.
     *
     * @see org.locationtech.udig.catalog.ICatalog#search(java.lang.String,
     *      com.vividsolutions.jts.geom.Envelope)
     * @param pattern
     * @param bbox used for an intersection test
     * @return
     */
    public synchronized List<IResolve> search( String pattern, Envelope bbox,
            IProgressMonitor monitor2 ) {
       
        if( CatalogPlugin.getDefault().isDebugging() ){
            if( Display.getCurrent() != null ){
                throw new IllegalStateException("search called from display thread");
            }
        }
       
        IProgressMonitor monitor = monitor2;
        if (monitor == null)
            monitor = new NullProgressMonitor();
        if ((pattern == null || "".equals(pattern.trim())) //$NON-NLS-1$
                && (bbox == null || bbox.isNull())) {
            return new LinkedList<IResolve>();
        }
        List<IResolve> result = new LinkedList<IResolve>();
        AST ast = ASTFactory.parse(pattern);
        if (ast == null) {
            return result;
        }
        HashSet<IService> searchScope = new HashSet<IService>();
        searchScope.addAll(this.services);
        try {
            monitor.beginTask(Messages.CatalogImpl_finding, searchScope.size() * 10);
            SERVICE: for( IService service : searchScope ) {
                ID serviceID = service.getID();
                if (check(service, ast)) {
                    result.add(service);
                }
                //Iterator< ? extends IGeoResource> resources;
                SubProgressMonitor submonitor = new SubProgressMonitor(monitor, 10);
                try {
                    List< ? extends IGeoResource> members = service.resources(submonitor);
                    if (members == null) {
                        continue SERVICE;
                    }
                    for( IGeoResource resource : members ) {
                        ID resoruceID = resource.getID();
                        try {
                            if (check(resource, ast, bbox)) {
                                result.add(resource);
                            }
                        } catch (Throwable t) {
                            CatalogPlugin.log("Could not search in resource:" + resoruceID, t);
                        }
                    }
                } catch (IOException e) {
                    CatalogPlugin.log("Could not search in service:" + serviceID, e);
                } finally {
                    submonitor.done();
                }
                Thread.yield(); // allow other threads to have a go... makes search view more responsive
            }
            return result;
        } finally {
            monitor.done();
        }
    }

    /* check the fields we catre about */
    protected static boolean check( IService service, AST pattern ) {
        if (pattern == null) {
            return false;
        }
        IServiceInfo info;
        try {
            info = service == null ? null : service.getInfo(null);
        } catch (IOException e) {
            info = null;
            CatalogPlugin.log(null, e);
        }
        boolean t = false;
        if (info != null) {
            if (info.getTitle() != null)
                t = pattern.accept(info.getTitle());
            if (!t && info.getKeywords() != null) {
                String[] keys = info.getKeywords().toArray(new String[0]);
                for( int i = 0; !t && i < keys.length; i++ )
                    if (keys[i] != null)
                        t = pattern.accept(keys[i]);
            }
            if (!t && info.getSchema() != null)
                t = pattern.accept(info.getSchema().toString());
            if (!t && info.getAbstract() != null)
                t = pattern.accept(info.getAbstract());
            if (!t && info.getDescription() != null)
                t = pattern.accept(info.getDescription());
        }
        return t;
    }

    /* check the fields we catre about */
    protected static boolean check( IGeoResource resource, AST pattern ) {
        if (pattern == null) {
            return true;
        }
        IGeoResourceInfo info;
        try {
            info = (resource == null ? null : resource.getInfo(null));
        } catch (IOException e) {
            CatalogPlugin.log(null, e);
            info = null;
        }
        if (info == null) {
            return false;
        }
        if (pattern.accept(info.getTitle())) {
            return true;
        }
        if (pattern.accept(info.getName())) {
            return true;
        }
        if (info.getKeywords() != null) {
            for( String key : info.getKeywords() ) {
                if (pattern.accept(key)) {
                    return true;
                }
            }
        }
        if (info.getSchema() != null && pattern.accept(info.getSchema().toString())) {
            return true;
        }
        if (pattern.accept(info.getDescription())) {
            return true;
        }
        return false;
    }

    protected static boolean check( IGeoResource resource, AST pattern, Envelope bbox ) {
        if (!check(resource, pattern)) {
            return false;
        }
        if (bbox == null || bbox.isNull()) {
            return true; // no checking here
        }
        try {
            ReferencedEnvelope bounds = resource.getInfo(null).getBounds();
            if (bounds == null) {
                return true; // bounds are unknown!
            }
            bounds = bounds.transform(DefaultGeographicCRS.WGS84, true);
            return bbox.intersects(bounds);
        } catch (Throwable e) {
            CatalogPlugin.log(null, e);
            return false;
        }
    }

    /**
     * Fire a resource changed event, these may be batched into one delta for performance.
     *
     * @param resoruce IGeoResource undergoing change
     * @param mask of IDelta constants indicating change
     * @throws IOException protected void fireResourceEvent( IGeoResource resource,
     *         IResolveDelta.Kind kind ) throws IOException { Object[] listeners =
     *         catalogListeners.getListeners(); if( listeners.length == 0 ) return;
     *         GeoReferenceDelta rDelta = new GeoReferenceDelta( resource, kind ); ServiceDelta
     *         sDelta = new ServiceDelta( resource.getService(null), IDelta.Kind.NO_CHANGE,
     *         Collections.singletonList( rDelta ) ); CatalogDelta cDelta = new CatalogDelta(
     *         Collections.singletonList( (IDelta)sDelta ) ); fire( new CatalogChangeEvent(
     *         resource, ICatalogChangeEvent.Type.POST_CHANGE, cDelta ) ); }
     */
    public void fire( IResolveChangeEvent event ) {
        if (catalogListeners.size() == 0)
            return;

        HashSet<IResolveChangeListener> copy;
        copy = getListenersCopy();

        for( IResolveChangeListener listener : copy ) {
            try {
                listener.changed(event);
            } catch (Throwable die) {
                CatalogPlugin.log("Catalog event could not be delivered to "
                        + listener.getClass().getSimpleName() + ":" + listener.toString(), die);
                die.printStackTrace();
            }
        }
    }

    /**
     * safely makes a copy of the listeners
     */
    private HashSet<IResolveChangeListener> getListenersCopy() {
        HashSet<IResolveChangeListener> copy;
        synchronized (catalogListeners) {
            copy = new HashSet<IResolveChangeListener>(catalogListeners);
        }
        return copy;
    }

    /**
     * @see org.locationtech.udig.catalog.ICatalog#resolve(java.lang.Class,
     *      org.eclipse.core.runtime.IProgressMonitor)
     * @SuppressWarnings(value={"unchecked" )
     */
    public <T> T resolve( Class<T> adaptee, IProgressMonitor monitor2 ) {
        IProgressMonitor monitor;
        if (monitor2 == null)
            monitor = new NullProgressMonitor();
        else
            monitor = monitor2;
        try {
            if (adaptee == null)
                return null;
            monitor.beginTask(Messages.CatalogImpl_resolving + adaptee.getSimpleName(), 2);
            monitor.worked(1);
            if (adaptee.isAssignableFrom(CatalogImpl.class))
                return adaptee.cast(this);
            if (adaptee.isAssignableFrom(CatalogInfoImpl.class))
                return adaptee.cast(metadata);
            if (adaptee.isAssignableFrom(services.getClass()))
                return adaptee.cast(services);
            if (adaptee.isAssignableFrom(List.class))
                return adaptee.cast(new LinkedList<IService>(services));
            if (adaptee.isAssignableFrom(catalogListeners.getClass()))
                return adaptee.cast(getListenersCopy());
        } finally {
            monitor.worked(1);
            monitor.done();
        }
        return null;
    }

    /*
     * @see org.locationtech.udig.catalog.IResolve#canResolve(java.lang.Class)
     */
    public <T> boolean canResolve( Class<T> adaptee ) {
        Object value = resolve(adaptee, null);
        return value != null;
    }

    /*
     * @see org.locationtech.udig.catalog.IResolve#members(org.eclipse.core.runtime.IProgressMonitor)
     */
    public List<IResolve> members( IProgressMonitor monitor2 ) {
        IProgressMonitor monitor = monitor2;
        if (monitor == null)
            monitor = new NullProgressMonitor();
        monitor.beginTask(Messages.CatalogImpl_finding, 1);
        monitor.done();
        return new LinkedList<IResolve>(services);
    }

    public String getTitle() {
        return metadata.getTitle();
    }

    /*
     * @see org.locationtech.udig.catalog.IResolve#getStatus()
     */
    public Status getStatus() {
        return Status.CONNECTED;
    }

    /*
     * @see org.locationtech.udig.catalog.IResolve#getMessage()
     */
    public Throwable getMessage() {
        return null;
    }

    /*
     * @see org.locationtech.udig.catalog.IResolve#getIdentifier()
     */
    public URL getIdentifier() {
        return metadata.getSource();
    }

    public ID getID() {
        return new ID(getIdentifier());
    }

    @Override
    public IGeoResource createTemporaryResource( Object descriptor ) {
        List<IConfigurationElement> list = ExtensionPointList
                .getExtensionPointList(TEMPORARY_RESOURCE_EXT_ID);
        for( IConfigurationElement element : list ) {
            try {
                String attribute = element.getAttribute("descriptorClass"); //$NON-NLS-1$
                Class< ? > c = descriptor.getClass().getClassLoader().loadClass(attribute);
                if (c.isAssignableFrom(descriptor.getClass())) {
                    TemporaryResourceFactory fac = (TemporaryResourceFactory) element
                            .createExecutableExtension("factory"); //$NON-NLS-1$
                    return fac.createResource(descriptor);
                }
            } catch (ClassNotFoundException e) {
                // thats fine. Lets allow tracing to get this.
                CatalogPlugin.trace("Trying to match classes", e); //$NON-NLS-1$
            } catch (Exception e) {
                throw (RuntimeException) new RuntimeException().initCause(e);
            }
        }
        throw new IllegalArgumentException(descriptor.getClass()
                + " is not a legal descriptor type.  If must be one of " + //$NON-NLS-1$
                String.valueOf(getTemporaryDescriptorClasses()));
    }

    @Override
    public synchronized String[] getTemporaryDescriptorClasses() {
        if (descriptors == null) {
            List<IConfigurationElement> list = ExtensionPointList
                    .getExtensionPointList(TEMPORARY_RESOURCE_EXT_ID);
            ArrayList<String> temp = new ArrayList<String>();
            for( IConfigurationElement element : list ) {
                try {
                    String desc = element.getAttribute("descriptorClass"); //$NON-NLS-1$
                    if (desc != null)
                        temp.add(desc);
                } catch (Exception e) {
                    CatalogPlugin.log("", e); //$NON-NLS-1$
                }
            }
            descriptors = temp.toArray(new String[temp.size()]);
        }
        int i = 0;
        if (descriptors != null)
            i = descriptors.length;
        String[] k = new String[i];
        if (descriptors != null)
            System.arraycopy(descriptors, 0, k, 0, k.length);
        return k;
    }

    public void loadFromFile( File catalogLocation, IServiceFactory factory ) {
        try {
            FileInputStream input = new FileInputStream(catalogLocation);
            IPreferencesService preferencesService = Platform.getPreferencesService();
            IExportedPreferences paramsNode = preferencesService.readPreferences(input);

            ServiceParameterPersister persister = new ServiceParameterPersister(this, factory, catalogLocation);
            Preferences parameterNode = findParameterNode(paramsNode);
           
            persister.restore(parameterNode);
        } catch (Throwable e) {
            CatalogPlugin.trace("Unable to restore catalog:"+e, e); //$NON-NLS-1$
            try {
                loadFromFileOld(factory, e);
                CatalogPlugin.trace("Restored from old catalog format",null);
            }
            catch (Throwable e2) {
                CatalogPlugin.log("Unable to restore from old catalog format", e); //$NON-NLS-1$
            }
        }
    }
    /**
     * Ok maybe it is an from an older version of uDig so try the oldCatalogRef.
     *
     * @param factory
     * @param e
     */
    public void loadFromFileOld(IServiceFactory factory, Throwable e) {
        IPreferencesService prefs = Platform.getPreferencesService();
        IEclipsePreferences root = prefs.getRootNode();
        Preferences node = root.node(InstanceScope.SCOPE).node(
                CatalogPlugin.ID + ".services"); //$NON-NLS-1$
        ServiceParameterPersister persister = new ServiceParameterPersister(this, factory);
        persister.restore(node);
    }

    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]);
    }
    /**
     * Save this local catalog to the provided file.
     *
     * @param catalogLocation
     * @param factory
     * @param monitor
     */
    public void saveToFile( File catalogLocation, IServiceFactory factory, IProgressMonitor monitor ) {
        try {
            Preferences toSave = Platform.getPreferencesService().getRootNode().node(
                    CatalogPlugin.ID).node("LOCAL_CATALOG_SERVICES"); //$NON-NLS-1$
            if (services != null) {
                ServiceParameterPersister persister = new ServiceParameterPersister(this, factory,
                        catalogLocation.getParentFile());

                persister.store(monitor, toSave, services);
            }

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

        } catch (Throwable t) {
            CatalogPlugin.log("Error saving services for the local catalog", t); //$NON-NLS-1$
        }
    }
    //
    // Interceptors
    //
    /**
     * Run the service interceptors for the indicated activity.
     *
     * @param activity ADDED_ID, CREATED_ID, REMOVED_ID
     */
    public static void runInterceptor( IService service, String activity ) {
        if (!ServiceInterceptor.ADDED_ID.equals(activity)
                && !ServiceInterceptor.REMOVED_ID.equals(activity)
                && !ServiceInterceptor.CREATED_ID.equals(activity)) {
            return; // no activities defined
        }
        List<IConfigurationElement> interceptors = ExtensionPointList
                .getExtensionPointList(ServiceInterceptor.EXTENSION_ID);

        for( IConfigurationElement element : interceptors ) {
            if (!activity.equals(element.getName())) {
                continue;
            }
            try {
                ServiceInterceptor interceptor = (ServiceInterceptor) element
                        .createExecutableExtension("class"); //$NON-NLS-1$
                interceptor.run(service);
            } catch (Exception e) {
                CatalogPlugin.trace( activity +" "+element.getAttribute("class")+":"+e, e); //$NON-NLS-1$
            }
        }
    }
   
    /**
     * Run the resource interceptors for the indicated activity.
     *
     * @param activity ADDED_ID, CREATED_ID, REMOVED_ID
     */
    public static void runInterceptor( IGeoResource resource, String activity ) {
        if (!GeoResourceInterceptor.ADDED_ID.equals(activity)
                && !GeoResourceInterceptor.REMOVED_ID.equals(activity)) {
            return; // no activities defined
        }
        List<IConfigurationElement> interceptors = ExtensionPointList
                .getExtensionPointList(ServiceInterceptor.EXTENSION_ID);

        for( IConfigurationElement element : interceptors ) {
            if (!activity.equals(element.getName())) {
                continue;
            }
            try {
                GeoResourceInterceptor interceptor = (GeoResourceInterceptor) element
                        .createExecutableExtension("class"); //$NON-NLS-1$
                interceptor.run(resource);
            } catch (Exception e) {
                CatalogPlugin.trace( activity +" "+element.getAttribute("class")+":"+e, e); //$NON-NLS-1$
            }
        }
    }
   
    /**
     * Takes a list of IServices and prioritises them by the percentage of metadata available. This
     * method relies on IServiceInfo.getMetric() for metadata metric calculations. Subclasses are
     * encouraged to override IServiceInfo getMetric() to calculate required metadata for each
     * services.
     *
     * @param services A list of IServices to be prioritised.
     * @param monitor Used to track the process of connecting
     * @return
     * @see #IserviceInfo.getMetric()
     */
    private List<IService> prioritise( List<IService> services, IProgressMonitor monitor ) {
       
        //If there is less than 2 IService there is no sorting required.
        if(services.size() < 2){
            return services;
        }
       
        final IProgressMonitor monitor2 = new SubProgressMonitor(monitor, 60);

        class IServiceComparator implements Comparator<IService> {
           
            public int compare( IService o1, IService o2 ) {
                try {
                    IServiceInfo info1 = o1.getInfo(new SubProgressMonitor(monitor2, 1));
                    IServiceInfo info2 = o2.getInfo(new SubProgressMonitor(monitor2, 1));
                    double metric1 = info1.getMetric();
                    double metric2 = info2.getMetric();
                   
                    if (metric1 > metric2) {
                        return 1;
                    }else if(metric1 < metric2){
                       return -1;
                    }else{
                       return 0;
                    }
                   
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                return 0;
            }
        }
      
        Comparator<IService> comparator = new IServiceComparator();
        Collections.sort(services, comparator);

        return services;
    }

    @Override
    public List<IService> checkMembers( List<IService> constructServiceList ) {
        List<IService> catalogServices = new ArrayList<IService>();

        for( IService service : constructServiceList ) {

            ID id = service.getID();
            IService found = getById(IService.class, id, new NullProgressMonitor());

            if (!(found == null)) {
                catalogServices.add(service);
            }

        }

        return catalogServices;
    }

    @Override
    public List<IService> checkNonMembers( List<IService> constructServiceList ) {
        List<IService> catalogServices = new ArrayList<IService>();

        for( IService service : constructServiceList ) {

            ID id = service.getID();
            IService found = getById(IService.class, id, new NullProgressMonitor());

            if (found == null) {
                catalogServices.add(service);
            }
        }

        return catalogServices;
    }

    @Override
    public List<IService> constructServices(URL url, IProgressMonitor monitor )
            throws IOException {
        if (monitor == null)
            monitor = new NullProgressMonitor();
       
        if (url == null){
            return null;
        }
       
        int urlProcessCount = 0;
       
        List<IService> availableServices = new ArrayList<IService>();//services already in catalog

        IServiceFactory factory = CatalogPlugin.getDefault().getServiceFactory();

        monitor.beginTask("Check", 1);
        monitor.subTask("Check available services");

        try {
            List<IService> possible = factory.createService(url);
            monitor.worked(urlProcessCount);

            IProgressMonitor monitor3 = new SubProgressMonitor(monitor, 60);
            monitor3.beginTask("connect", possible.size() * 10);

            for( Iterator<IService> iterator = possible.iterator(); iterator.hasNext(); ) {
                IService service = iterator.next();

                if (service == null) continue;

                monitor3.subTask("connect " + service.getID());
                try {
                    // try connecting
                    IServiceInfo info = service.getInfo(new SubProgressMonitor(monitor3, 10));

                    if (info == null) {
                        CatalogPlugin.trace("unable to connect to " + service.getID(), null);
                        continue; // skip unable to connect
                    }

                    availableServices.add(service);
                } catch (Throwable t) {
                    // usually indicates an IOException as the service is unable to connect
                    CatalogPlugin.trace("trouble connecting to " + service.getID(), t);
                }
            }
            monitor3.done();
        } finally {
            monitor.done();
        }
        return prioritise(availableServices, monitor); //return a prioritise list
    }

    @Override
    public List<IService> constructServices( Map<String, Serializable> params,
            IProgressMonitor monitor ) throws IOException {
        if (monitor == null)
            monitor = new NullProgressMonitor();

        List<IService> availableServices = new ArrayList<IService>();//services already in catalog

        IServiceFactory factory = CatalogPlugin.getDefault().getServiceFactory();
       
        // IOException used to report a problem connecting; usually we only have one
        // Service willing to try with a given set of parameters so this works out okay
        //
        IOException eek = null;
        try {

            if (params != null && !params.isEmpty()) {
                List<IService> createdServices = factory.createService(params);
                Set<IService> results = new HashSet<IService>(createdServices);
                if( results.isEmpty() ){
                    return createdServices; // nothing was willing to try connecting!
                }
                for( IService service : results ) {
                    try {
                        IServiceInfo info = service.getInfo(new SubProgressMonitor(monitor, 10));
                        if (info == null) {
                            CatalogPlugin.trace("unable to connect to " + service.getID(), null);
                            continue; // skip unable to connect
                        }
                        availableServices.add(service);
                    }
                    catch( Throwable t ){
                        // unable to use the service - getInfo did not return correctly!
                        if( t instanceof IOException ){
                            eek = (IOException) t;
                        }
                        else {
                            t.printStackTrace();
                        }
                    }
                }

            }
        } finally {
            monitor.done();
        }
        if( eek != null ){
            throw eek; // unable to connect due to error!
        }
       
        return prioritise(availableServices, monitor); //return a prioritise list
    }
}
TOP

Related Classes of org.locationtech.udig.catalog.internal.CatalogImpl

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.