net.refractions.udig.catalog.internal.CatalogImpl.java Source code

Java tutorial

Introduction

Here is the source code for net.refractions.udig.catalog.internal.CatalogImpl.java

Source

/*
 *    uDig - User Friendly Desktop Internet GIS client
 *    http://udig.refractions.net
 *    (C) 2004, Refractions Research Inc.
 *
 *    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;
 *    version 2.1 of the License.
 *
 *    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.
 *
 */
package net.refractions.udig.catalog.internal;

import static net.refractions.udig.catalog.IResolve.Status.CONNECTED;
import static net.refractions.udig.catalog.IResolve.Status.NOTCONNECTED;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;

import com.vividsolutions.jts.geom.Envelope;

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

import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor;

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;

/**
 * Implementation of an in memory catalog.
 * 
 * @author David Zwiers, Refractions Research
 * @since 0.6
 */
public class CatalogImpl extends ICatalog {

    private static Log log = LogFactory.getLog(CatalogImpl.class);

    private static final String TEMPORARY_RESOURCE_EXT_ID = "net.refractions.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 net.refractions.udig.catalog.ICatalog#addCatalogListener(net.refractions.udig.catalog.ICatalog.ICatalogListener)
     * @param listener
     */
    public void addCatalogListener(IResolveChangeListener listener) {
        catalogListeners.add(listener);
    }

    public void addListener(IResolveChangeListener listener) {
        catalogListeners.add(listener);
    }

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

    public void removeListener(IResolveChangeListener listener) {
        catalogListeners.remove(listener);
    }

    /**
     * @see net.refractions.udig.catalog.ICatalog#add(net.refractions.udig.catalog.IService)
     * @param entry
     * @throws UnsupportedOperationException
     */
    public void add(IService entry) throws UnsupportedOperationException {
        if (entry == null || entry.getIdentifier() == null) {
            throw new NullPointerException("Cannot have a null id"); //$NON-NLS-1$
        }
        if (getById(IService.class, entry.getID(), new NullProgressMonitor()) != null) {
            throw new IllegalArgumentException("Catalog: entry already exists: " + entry.getID());
        }
        services.add(entry);
        IResolveDelta deltaAdded = new ResolveDelta(entry, IResolveDelta.Kind.ADDED);
        IResolveDelta deltaChanged = new ResolveDelta(this, Collections.singletonList(deltaAdded));
        fire(new ResolveChangeEvent(CatalogImpl.this, IResolveChangeEvent.Type.POST_CHANGE, deltaChanged));
        log.info("catalog size:" + services.size());
    }

    /**
     * @see net.refractions.udig.catalog.ICatalog#remove(net.refractions.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);
        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);

        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);
        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);
        }
        fire(event);
    }

    public List<IResolve> find(ID id, IProgressMonitor monitor) {
        return find(id.toURL(), monitor);
    }

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

        ID id = 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();
            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));
                    }
                }
            }
        }
        // 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);
    }

    /**
     * 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(), "net.refractions.udig.catalog.friendly", //$NON-NLS-1$
                new ExtensionPointProcessor() {
                    /**
                     * 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 != null) ? monitor : new NullProgressMonitor();
        if (id == null) {
            // _p3: otherwise loading just does nothing because
            throw new IllegalArgumentException("id musr not be 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. <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) {
            if (new ID(id.toURL()).equals(new ID(handle.getIdentifier()))) {
                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 net.refractions.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) {
        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>();
        }

        AST ast = null;
        if (pattern != null && !"".equals(pattern.trim())) //$NON-NLS-1$
            ast = ASTFactory.parse(pattern);

        // TODO check cuncurrency issues here

        List<IResolve> result = new LinkedList<IResolve>();
        HashSet<IService> tmp = new HashSet<IService>();
        tmp.addAll(this.services);
        try {
            monitor.beginTask(Messages.CatalogImpl_finding, tmp.size() * 10);
            Iterator<IService> services = tmp.iterator();
            if (services != null) {
                while (services.hasNext()) {
                    IService service = services.next();
                    if (check(service, ast)) {
                        result.add(service);
                    }
                    Iterator<? extends IGeoResource> resources;
                    SubProgressMonitor submonitor = new SubProgressMonitor(monitor, 10);
                    try {
                        List<? extends IGeoResource> t = service.resources(submonitor);
                        resources = t == null ? null : t.iterator();
                        while (resources != null && resources.hasNext()) {
                            IGeoResource resource = resources.next();
                            if (check(resource, ast, bbox)) {
                                result.add(resource);
                            }
                        }
                    } catch (IOException e) {
                        CatalogPlugin.log(null, e);
                    } finally {
                        submonitor.done();
                    }
                }
            }
            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;
        }
        boolean t = false;
        if (info != null) {
            if (info.getTitle() != null)
                t = pattern.accept(info.getTitle());
            if (!t && info.getName() != null)
                t = pattern.accept(info.getName());
            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.getDescription() != null)
                t = pattern.accept(info.getDescription());
        }
        return t;
    }

    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 {
            return bbox.intersects(resource.getInfo(null).getBounds());
        } 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(null, new Exception(die));
            }
        }
    }

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

    /**
     * @see net.refractions.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 net.refractions.udig.catalog.IResolve#canResolve(java.lang.Class)
     */
    public <T> boolean canResolve(Class<T> adaptee) {
        Object value = resolve(adaptee, null);
        return value != null;
    }

    /*
     * @see net.refractions.udig.catalog.IResolve#members(org.eclipse.core.runtime.IProgressMonitor)
     */
    public List<IResolve> members(IProgressMonitor monitor2) {
        IProgressMonitor monitor = monitor2 != null ? monitor2 : new NullProgressMonitor();
        monitor.beginTask(Messages.CatalogImpl_finding, 1);

        // _p3: group services into folders for each service type
        List<ServiceTypeFolder> folders = new ArrayList<ServiceTypeFolder>();
        for (IResolve service : services) {
            boolean found = false;
            for (ServiceTypeFolder folder : folders) {
                if (folder.type.isAssignableFrom(service.getClass())) {
                    folder.services.add(service);
                    found = true;
                    break;
                }
            }
            if (!found) {
                folders.add(new ServiceTypeFolder(this, service));
            }
        }
        monitor.done();

        //return new LinkedList<IResolve>(services);
        List<IResolve> result = new ArrayList(folders);
        //       for (ServiceTypeFolder folder : folders) {
        //            result.add( folder );
        //        }
        return result;
    }

    /**
     * A folder for all service of a type. 
     *
     * @author <a href="http://www.polymap.de">Falko Braeutigam</a>
     * @version POLYMAP3 ($Revision$)
     * @since 3.0
     */
    static class ServiceTypeFolder implements IResolveFolder, IResolve {

        private CatalogImpl catalog;

        private List<IResolve> services = new ArrayList();

        private Class<? extends IResolve> type;

        private URL id;

        public ServiceTypeFolder(CatalogImpl catalog, IResolve service) {
            this.catalog = catalog;
            this.services.add(service);
            this.type = service.getClass();
            try {
                id = new URL("http://localhost/" + type.getSimpleName()); //$NON-NLS-1$
            } catch (MalformedURLException e) {
                // do nothing
            }
        }

        public ImageDescriptor getIcon(IProgressMonitor monitor) {
            return null;
        }

        public IService getService(IProgressMonitor monitor) {
            throw new RuntimeException("not yet implemented.");
        }

        public String getTitle() {
            String typeName = type.getSimpleName();
            if (StringUtils.contains(typeName, "Post")) {
                return "PostGIS";
            } else if (StringUtils.contains(typeName, "Shp")) {
                return "Shapefile";
            } else if (StringUtils.contains(typeName, "WMS")) {
                return "WMS";
            } else if (StringUtils.contains(typeName, "WFS")) {
                return "WFS";
            } else if (StringUtils.contains(typeName, "Oracle")) {
                return "Oracle";
            } else if (StringUtils.contains(typeName, "MySQL")) {
                return "MySQL";
            } else if (StringUtils.containsIgnoreCase(typeName, "GeoTiff")) {
                return "GeoTiff";
            } else {
                return StringUtils.removeEnd(typeName, "Impl");
            }
        }

        public void dispose(IProgressMonitor monitor) {
        }

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

        public URL getIdentifier() {
            return id;
        }

        public Throwable getMessage() {
            return null;
        }

        public Status getStatus() {
            return Status.CONNECTED;
        }

        public List<IResolve> members(IProgressMonitor monitor) throws IOException {
            return Collections.unmodifiableList(services);
        }

        public IResolve parent(IProgressMonitor monitor) throws IOException {
            return catalog;
        }

        public <T> boolean canResolve(Class<T> adaptee) {
            throw new RuntimeException("not yet implemented.");
        }

        public <T> T resolve(Class<T> adaptee, IProgressMonitor monitor) throws IOException {
            throw new RuntimeException("not yet implemented.");
        }

    }

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

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

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

    /*
     * @see net.refractions.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);

            persister.restore(findParameterNode(paramsNode));
            fire(new ResolveChangeEvent(CatalogImpl.this, IResolveChangeEvent.Type.POST_CHANGE, null));
        } catch (Throwable e) {
            // ok maybe it is an from an older version of uDig so try the oldCatalogRef
            try {
                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);
            } catch (Throwable e2) {
                e.printStackTrace();
                CatalogPlugin.log("Unable to load services", 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]);
    }

    /**
     * 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$ 
        }
    }

}