Java tutorial
/* * 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; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.io.IOException; import java.io.Serializable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.google.common.base.Joiner; import net.refractions.udig.catalog.internal.Messages; import net.refractions.udig.ui.ErrorManager; import org.eclipse.ui.model.IWorkbenchAdapter; import org.eclipse.ui.model.WorkbenchAdapter; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.SubProgressMonitor; /** * Represents a geo spatial service handle. Follows the same design as IResource. * <p> * Represents a spatial service, which may be lazily loaded. The existance of this object does not * ensure that the advertized data is guaranteed to exist, nor does this interface guarantee that * the service exists based on this object's existance. We should also note the resource management * is left to the user, and that resolve() is not guaranteed to return the same instance object from * two subsequent calls, but may. This is merely a handle to some information about a service, and a * method of aquiring an instance of the service ... * </p> * <p> * NOTE: This may be the result of communications with a metadata service, and as such this service * described may not be running right now. Remember to check the service status. * </p> * * <h2>Implementing an IService</h2> * * Implement the abstract methods and you are good to go. * * <h2>Extending an IService</h2> * * You may want to implement your own IService in order to provide a handle for a new kind of <i>API</i>. * <ol> * <li>New method:<pre><code> * public API getAPI( ProgressMonitor monitor){ * if (monitor == null) monitor = new NullProgressMonitor(); * * monitor.beingTask("Connect to API",2); * try { * String server = getConnectionParams().get("server"); * monitor.worked(1); * return new API( s ); * } * finally { * monitor.done(); * } * } * </pre></code> * (note the use of NullProgressMonitor) * </li> * * <li>Optional: Customize resolve method to advertise your new API "dynamically"<pre><code> * public <T> boolean canResolve( Class<T> adaptee ) { * return adaptee != null * && (adaptee.isAssignableFrom(API.class) || super.canResolve(adaptee)); * } * public <T> T resolve( Class<T> adaptee, IProgressMonitor monitor ) throws IOException { * if (monitor == null) monitor = new NullProgressMonitor(); * if (adaptee == null) * throw new NullPointerException("No adaptor specified" ); * * if (adaptee.isAssignableFrom(API.class)) { * return adaptee.cast(getAPI(monitor)); * } * return super.resolve(adaptee, monitor); * } * </code></pre> * (note the call to super) * </li> * <li>Optional: cache your API as the "connection"<pre><code> * API api = null; * Throwable msg = null; * public synchronized API getAPI( ProgressMonitor monitor){ * if( api != null ) return api; * * if (monitor == null) monitor = new NullProgressMonitor(); * * monitor.beingTask("Connect to API",2); * try { * String server = getConnectionParams().get("server"); * monitor.worked(1); * api = new API( s ); * monitor.worked(1); * return api; * } * finally { * monitor.done(); * } * } * public Status getStatus() { * return msg != null? Status.BROKEN : api == null? Status.NOTCONNECTED : Status.CONNECTED; * } * public Throwable getMessage(){ * return msg; * } * public synchronized void dispose( ProgressMonitor monitor ){ * if( api != null ){ * api.dispose(); * api = null; * } * if( msg != null ) msg = null; * } * </code></pre> * (Note the use of getMessage and getStatus) * </li> * </ol> * * @author David Zwiers, Refractions Research * @since 0.6 * @see IServiceInfo * @see IServiceFactory */ public abstract class IService implements IResolve, IAdaptable { private static Log log = LogFactory.getLog(IService.class); /** * Used to save persisted properties; please see ServiceParameterPersister for details. */ private Map<String, Serializable> properties = Collections.synchronizedMap(new HashMap<String, Serializable>()); /** * This is a protected field; that is lazily created when getInfo is called. */ protected volatile IServiceInfo info = null; /** * _p3: Make the resolve mechanism available via the {@link IAdaptable} * interface. This helps to add eclipse/workbench related APIs to service, * such as the ACL API. * <p> * I recently found {@link IResolveAdapterFactory}; but this does not seem * to work for all types (?) */ public Object getAdapter(Class adapter) { // try resolve try { Object resolved = resolve(adapter, null); if (resolved != null) { log.info("getAdapter(): adapter= " + adapter.getSimpleName() + ", resolved= " + resolved); return resolved; } } catch (IOException e) { log.warn("getAdapter(): ...", e); } // WorkbenchAdapter to support labeling if (IWorkbenchAdapter.class.isAssignableFrom(adapter)) { return new WorkbenchAdapter() { public String getLabel(Object object) { return getTitle(); } }; } // try AdapterManager Object result = Platform.getAdapterManager().getAdapter(this, adapter); log.info("getAdapter(): adapter= " + adapter.getSimpleName() + ", result= " + result); return result; } /** * Will attempt to morph into the adaptee, and return that object. Harded coded to capture the * IService contract. * <p> * That is we *must* resolve the following: * <ul> * <li>IService: this * <li>IServiceInfo.class: getInfo * <li>ICatalog.class: parent * </ul> * Recomended adaptations: * <ul> * <li>ImageDescriptor.class: for a custom icon * <li>List.class: members * </ul> * May Block. * <p> * Example implementation:<pre><code> * public <T> T resolve( Class<T> adaptee, IProgressMonitor monitor ) throws IOException { * if (monitor == null) monitor = new NullProgressMonitor(); * if (adaptee == null) * throw new NullPointerException("No adaptor specified" ); * * if (adaptee.isAssignableFrom(API.class)) { * return adaptee.cast(getAPI(monitor)); * } * return super.resolve(adaptee, monitor); * } * </code></pre> * @param adaptee * @param monitor * @return instance of adaptee, or null if unavailable (IServiceInfo and List<IGeoResource> * must be supported) * @see IServiceInfo * @see IGeoResource * @see IResolve#resolve(Class, IProgressMonitor) */ public <T> T resolve(Class<T> adaptee, IProgressMonitor monitor) throws IOException { if (monitor == null) monitor = new NullProgressMonitor(); if (adaptee == null) { throw new NullPointerException("No adaptor specified"); //$NON-NLS-1$ } if (adaptee.isAssignableFrom(IServiceInfo.class)) { return adaptee.cast(createInfo(monitor)); } if (adaptee.isAssignableFrom(IService.class)) { monitor.done(); return adaptee.cast(this); } if (adaptee.isAssignableFrom(ICatalog.class)) { return adaptee.cast(parent(monitor)); } IResolveManager rm = CatalogPlugin.getDefault().getResolveManager(); if (rm.canResolve(this, adaptee)) { return rm.resolve(this, adaptee, monitor); } return null; // no adapter found (check to see if ResolveAdapter is registered?) } /** * Harded coded to capture the IService contract. * <p> * That is we *must* resolve the following: * <ul> * <li>IService: this * <li>IServiceInfo.class: getInfo * <li>List.class: members * <li>ICatalog.class: parent * </ul> * <p> * Here is an implementation example (for something that can adapt to DataStore): * * <pre><code> * public <T> boolean canResolve( Class<T> adaptee ) { * return adaptee != null * && (adaptee.isAssignableFrom(DataStore.class) || super.canResolve(adaptee)); * } * </code></pre> */ public <T> boolean canResolve(Class<T> adaptee) { return adaptee != null && (adaptee.isAssignableFrom(IService.class) || // this adaptee.isAssignableFrom(IServiceInfo.class) || // getInfo adaptee.isAssignableFrom(List.class) || // members adaptee.isAssignableFrom(ICatalog.class) || // parent CatalogPlugin.getDefault().getResolveManager().canResolve(this, adaptee)); } /** * Returns LocalCatalog by defaul, subclass must override iff a custom catalog is used. */ public ICatalog parent(IProgressMonitor monitor) { ICatalog localCatalog = CatalogPlugin.getDefault().getLocalCatalog(); if (localCatalog.getById(IService.class, getID(), monitor) != null) { return localCatalog; } else { return null; } } /** * List of concrete resources (IE with data) managed by this service. * <p> * Many file based services will just contain a single IGeoResource. Services * that arrange their data into folders will (like WMS) will return a list of * all the actual data made available by the service. * </p> * The IService.resources() method is the inverse of GeoResource.service(). */ public abstract List<? extends IGeoResource> resources(IProgressMonitor monitor) throws IOException; /** * The contents of this IService presented as a list of members. * <p> * For many simple services this will be the same as the the resources, however * some services may choose to organize their resources into folders or schemas, * or even a tree (as is the case for WMS layers). */ public List<IResolve> members(IProgressMonitor monitor) throws IOException { List<IResolve> resolves = new ArrayList<IResolve>(resources(monitor)); return resolves; } /** * Responsible for creation of an appropriate IServiceInfo object. * * @return IServiceInfo resolve(IServiceInfo.class, IProgressMonitor monitor); * @throws IOException */ protected abstract IServiceInfo createInfo(IProgressMonitor monitor) throws IOException; /** * Information about this service. * * @return IServiceInfo resolve(IServiceInfo.class,IProgressMonitor monitor); * @see IService#resolve(Class, IProgressMonitor) */ public final IServiceInfo getInfo(IProgressMonitor monitor) throws IOException { if (info == null) { //lazy creation synchronized (this) { //support concurrent access if (info == null) { info = createInfo(monitor); } } } return info; } public ID getID() { return new ID(getIdentifier()); } /** * Accessor to the set of params used to create this entry. There is no guarantee that these * params created a usable service (@see getStatus() ). These params may have been modified * within the factory during creation. This method is intended to be used for cloning (@see * IServiceFactory) or for persistence between sessions. * * <p> * <b>IMPORTANT:</b> Because of the serialization currently used only types that can be reconstructed from their toString() representation * can be used. For example: * <pre><code> * valueThatIsSaved=url.toString(). * URL restoredValue=new URL(valueThatIsSaved); * </code></pre> * Also only classes that this plugin can load can be loaded so custom classes from downstream plugins cannot be used. * It is recommended that only "normal" types be used like Integer, URL, Float, etc... * * </p> * This restriction will be lifted in the future. (Except for the loading issue that is a design issue that we will live with.) * @see IServiceFactory * @return */ public abstract Map<String, Serializable> getConnectionParams(); /** * Returns a copy of the map of this resource's persistent properties. Returns an empty map if this resource has no persistent properties. * @return The map containing the persistent properties where the key is the {@link QualifiedName} of the property and the value is the {@link String} value of the property. */ public Map<String, Serializable> getPersistentProperties() { return properties; } /** * Retrieves the title from the IService cache, or from the ServiceInfo * object iff it is present. Returns null if either of these are not * available. If the title is fetched from ServiceInfo, it is added to the * cache before returning. * * @returns the service title or null if non is readily available */ public String getTitle() { String title = null; if (info != null) { // we are connected and can use the real title title = info.getTitle(); // cache title for when we are next offline getPersistentProperties().put("title", title); //$NON-NLS-1$ } if (title == null) { Serializable s = properties.get("title"); //$NON-NLS-1$ title = (s != null ? s.toString() : null); } return title; } /** * This should represent the identifier * * @see Object#equals(java.lang.Object) * @param obj * @return */ public final boolean equals(Object obj) { if (obj != null && obj instanceof IService) { IService service = (IService) obj; if (getID() != null && service.getID() != null) return getID().equals(service.getID()); } return false; } /** * This should represent the identified * * @see Object#hashCode() * @return */ public final int hashCode() { int value = 31; if (getID() != null) value += 31 + getID().hashCode(); value += 31 + getClass().getName().hashCode(); return value; } /** * Indicate class and id. * * @return string representing this IResolve */ public String toString() { return Joiner.on("").join(getClass().getSimpleName(), " (", getIdentifier(), ")"); } public void dispose(IProgressMonitor monitor) { monitor.beginTask(Messages.IService_dispose, 100); List<? extends IResolve> members = Collections.emptyList(); try { if (getStatus() == Status.CONNECTED) { // only ask for members if we are connected (if not we just get an error trying to connect again) members = members(new SubProgressMonitor(monitor, 1)); } } catch (Throwable e) { ErrorManager.get().displayException(e, "Cleaning up memebers of service: " + getIdentifier(), //$NON-NLS-1$ CatalogPlugin.ID); return; } int steps = (int) ((double) 99 / (double) members.size()); for (IResolve resolve : members) { try { SubProgressMonitor subProgressMonitor = new SubProgressMonitor(monitor, steps); resolve.dispose(subProgressMonitor); subProgressMonitor.done(); } catch (Throwable e) { ErrorManager.get().displayException(e, "Error disposing member of service: " + resolve.getIdentifier(), CatalogPlugin.ID); //$NON-NLS-1$ } } } }