/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wps.remote; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.DataStoreInfo; import org.geoserver.catalog.DimensionInfo; import org.geoserver.catalog.DimensionPresentation; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.StyleInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.catalog.impl.CoverageInfoImpl; import org.geoserver.catalog.impl.DimensionInfoImpl; import org.geoserver.config.GeoServer; import org.geoserver.importer.ImportContext; import org.geoserver.importer.ImportTask; import org.geoserver.importer.Importer; import org.geoserver.importer.SpatialFile; import org.geoserver.platform.ExtensionPriority; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.platform.GeoServerResourceLoader; import org.geoserver.wps.remote.plugin.XMPPClient; import org.geotools.coverage.grid.io.DimensionDescriptor; import org.geotools.coverage.grid.io.StructuredGridCoverage2DReader; import org.geotools.util.logging.Logging; import org.opengis.coverage.grid.GridCoverageReader; import org.opengis.feature.type.Name; import org.opengis.util.ProgressListener; import org.springframework.beans.factory.DisposableBean; import org.springframework.core.io.Resource; /** * Base class for the remote clients implementations. Those implementations will be plugged into GeoServer through the Spring app-context. * * @author Alessio Fabiani, GeoSolutions * */ public abstract class RemoteProcessClient implements DisposableBean, ExtensionPriority { /** The LOGGER */ public static final Logger LOGGER = Logging.getLogger(XMPPClient.class.getPackage().getName()); /** Whether this client is enabled or not from configuration */ private boolean enabled; /** Whenever more instances of the client are available, they should be ordered by ascending priority */ private int priority; /** The {@link RemoteProcessFactoryConfigurationWatcher} implementation */ private final RemoteProcessFactoryConfigurationWatcher remoteProcessFactoryConfigurationWatcher; /** The registered {@link RemoteProcessFactoryListener} */ private Set<RemoteProcessFactoryListener> remoteFactoryListeners = Collections .newSetFromMap(new ConcurrentHashMap<RemoteProcessFactoryListener, Boolean>()); /** The registered {@link RemoteProcessClientListener} */ private Set<RemoteProcessClientListener> remoteClientListeners = Collections .newSetFromMap(new ConcurrentHashMap<RemoteProcessClientListener, Boolean>()); /** The available Registered Processing Machines */ protected List<RemoteMachineDescriptor> registeredProcessingMachines = Collections .synchronizedList(new ArrayList<RemoteMachineDescriptor>()); /** */ protected List<RemoteRequestDescriptor> pendingRequests = Collections .synchronizedList(new LinkedList<RemoteRequestDescriptor>()); /** */ protected File certificateFile = null; /** */ protected String certificatePassword = null; /** * The default Cosntructor * * @param remoteProcessFactory */ public RemoteProcessClient( RemoteProcessFactoryConfigurationWatcher remoteProcessFactoryConfigurationWatcher, boolean enabled, int priority) { this.remoteProcessFactoryConfigurationWatcher = remoteProcessFactoryConfigurationWatcher; this.enabled = enabled; this.priority = priority; } /** * @return the {@link RemoteProcessFactoryConfiguration} object */ public RemoteProcessFactoryConfiguration getConfiguration() { return this.remoteProcessFactoryConfigurationWatcher.getConfiguration(); } /** * Initialization method * */ public abstract void init() throws Exception; /** * Destroy method * */ public abstract void destroy() throws Exception; /** * @return the remoteFactoryListeners */ public Set<RemoteProcessFactoryListener> getRemoteFactoryListeners() { return remoteFactoryListeners; } /** * @return the remoteClientListeners */ public Set<RemoteProcessClientListener> getRemoteClientListeners() { return remoteClientListeners; } /** * @param enabled the enabled to set */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /** * @return the registeredProcessingMachines */ public List<RemoteMachineDescriptor> getRegisteredProcessingMachines() { return registeredProcessingMachines; } /** * @param registeredProcessingMachines the registeredProcessingMachines to set */ public void setRegisteredProcessingMachines( List<RemoteMachineDescriptor> registeredProcessingMachines) { this.registeredProcessingMachines = registeredProcessingMachines; } /** * Whether the plugin is enabled or not. * * */ public boolean isEnabled() { return this.enabled; } /** * @return the priority */ public int getPriority() { return priority; } /** * Set the KeyStore Certificate Path * * @param certificateFile * @throws IOException */ public void setCertificateFile(Resource certificateFile) throws IOException { this.certificateFile = certificateFile.getFile(); } /** * Set the KeyStore Certificate Password * * @param certificatePassword */ public void setCertificatePassword(String certificatePassword) { this.certificatePassword = certificatePassword; } /** * @param priority the priority to set */ public void setPriority(int priority) { this.priority = priority; } /** * Registers the {@link RemoteProcessFactoryListener} remoteClientListeners * * @param listener */ public void registerProcessFactoryListener(RemoteProcessFactoryListener listener) { remoteFactoryListeners.add(listener); } /** * De-registers the {@link RemoteProcessFactoryListener} remoteClientListeners * * @param listener */ public void deregisterProcessFactoryListener(RemoteProcessFactoryListener listener) { remoteFactoryListeners.remove(listener); } /** * Registers the {@link RemoteProcessClientListener} remoteClientListeners * * @param listener */ public void registerProcessClientListener(RemoteProcessClientListener listener) { remoteClientListeners.add(listener); } /** * De-registers the {@link RemoteProcessClientListener} remoteClientListeners * * @param listener */ public void deregisterProcessClientListener(RemoteProcessClientListener listener) { remoteClientListeners.remove(listener); } /** * Invoke the {@link RemoteProcessClient} execution * * @param name * @param input * @param metadata * @param monitor * */ public abstract String execute(Name name, Map<String, Object> input, Map<String, Object> metadata, ProgressListener monitor) throws Exception; /** * Accessor for global geoserver instance from the test application context. */ public GeoServer getGeoServer() { return (GeoServer) GeoServerExtensions.bean("geoServer"); } /** * Accessor for global geoserver instance from the test application context. */ public Importer getImporter() { return (Importer) GeoServerExtensions.bean("importer"); } /** * * @param wsName * @param dsName * */ public DataStoreInfo createH2DataStore(String wsName, String dsName) { // create a datastore to import into Catalog cat = getGeoServer().getCatalog(); WorkspaceInfo ws = wsName != null ? cat.getWorkspaceByName(wsName) : cat.getDefaultWorkspace(); DataStoreInfo ds = cat.getFactory().createDataStore(); ds.setWorkspace(ws); ds.setName(dsName); ds.setType("H2"); GeoServerResourceLoader loader = cat.getResourceLoader(); final String dataDir = loader.getBaseDirectory().getAbsolutePath(); Map params = new HashMap(); params.put("database", dataDir + "/" + dsName); params.put("dbtype", "h2"); params.put("namespace", cat.getNamespaceByPrefix(ws.getName()).getURI()); ds.getConnectionParameters().putAll(params); ds.setEnabled(true); cat.add(ds); return ds; } /** * @param metadata * @param value * * @throws IOException */ public LayerInfo importLayer(File file, String type, DataStoreInfo store, String name, String title, String description, String defaultStyle, String targetWorkspace, String metadata) throws Exception { Importer importer = getImporter(); LOGGER.fine(" - [Remote Process Client - importLayer] Importer Context from Spatial File:" + file.getAbsolutePath()); ImportContext context = (store != null ? importer.createContext(new SpatialFile(file), store) : importer.createContext(new SpatialFile(file))); if (context.getTasks() != null && context.getTasks().size() > 0) { WorkspaceInfo ws = null; ImportTask task = context.getTasks().get(0); if (targetWorkspace != null) { LOGGER.fine( " - [Remote Process Client - importLayer] Looking for Workspace in the catalog:" + targetWorkspace); ws = importer.getCatalog().getWorkspaceByName(targetWorkspace); if (ws != null) { LOGGER.fine(" - [Remote Process Client - importLayer] Workspace found:" + ws); context.setTargetWorkspace(ws); } else { LOGGER.fine( " - [Remote Process Client - importLayer] Workspace *NOT* found - using the Default one:" + importer.getCatalog().getDefaultWorkspace()); context.setTargetWorkspace(importer.getCatalog().getDefaultWorkspace()); } } if (defaultStyle != null) { StyleInfo style = importer.getCatalog().getStyleByName(defaultStyle); if (style == null && targetWorkspace != null) { style = importer.getCatalog().getStyleByName(targetWorkspace, defaultStyle); } if (style != null) { task.getLayer().setDefaultStyle(style); } } if (name != null) { task.getLayer().setName(name); } if (title != null) { task.getLayer().setTitle(title); } if (description != null) { task.getLayer().setAbstract(description); } if (metadata != null) { task.getLayer().getMetadata().put("owc_properties", metadata); } // AF: Importer ISSUE -> The target workspace is not honored /*if (task.getLayer().getResource() != null) { if (ws != null) { task.getLayer().getResource().getStore().setWorkspace(ws); } if (task.getLayer().getResource() instanceof CoverageInfo) { ((CoverageInfoImpl) task.getLayer().getResource()).setNativeCoverageName(name); } }*/ importer.run(context); for (int importChecks=0; importChecks<10; importChecks++) { if (context.getState() == ImportContext.State.COMPLETE) { if (context.getTasks() != null && context.getTasks().size() > 0) { // ImportTask task = context.getTasks().get(0); // assertEquals(ImportTask.State.READY, task.getState()); // assertEquals("the layer name", task.getLayer().getResource().getName()); task = context.getTasks().get(0); // WARNING: The Importer Configures Just The First Layer if (task.getLayer().getResource() instanceof CoverageInfo) { CoverageInfo ci = ((CoverageInfo) task.getLayer().getResource()); GridCoverageReader reader = null; try { reader = ci.getGridCoverageReader(null, null); String[] cvNames = reader.getGridCoverageNames(); if (cvNames != null && cvNames.length > 0) { final String nativeCoverageName = cvNames[0]; ci.setNativeCoverageName(nativeCoverageName); // if(type.equals("application/x-netcdf")) Set Dimensions if (reader instanceof StructuredGridCoverage2DReader) { StructuredGridCoverage2DReader structuredReader = ((StructuredGridCoverage2DReader) reader); // Getting dimension descriptors final List<DimensionDescriptor> dimensionDescriptors = structuredReader .getDimensionDescriptors(nativeCoverageName); DimensionDescriptor timeDimension = null; DimensionDescriptor elevationDimension = null; final List<DimensionDescriptor> customDimensions = new ArrayList<DimensionDescriptor>(); // Collect dimension Descriptor info for (DimensionDescriptor dimensionDescriptor : dimensionDescriptors) { if (dimensionDescriptor.getName() .equalsIgnoreCase(ResourceInfo.TIME)) { timeDimension = dimensionDescriptor; } else if (dimensionDescriptor.getName() .equalsIgnoreCase(ResourceInfo.ELEVATION)) { elevationDimension = dimensionDescriptor; } else { customDimensions.add(dimensionDescriptor); } } final boolean defaultTimeNeeded = timeDimension != null; final boolean defaultElevationNeeded = elevationDimension != null; // Create Default Time Dimension If Needed if (defaultTimeNeeded) { DimensionInfo di = new DimensionInfoImpl(); di.setEnabled(true); di.setPresentation(DimensionPresentation.LIST); di.setAttribute(timeDimension.getStartAttribute()); ci.getMetadata().put(ResourceInfo.TIME, di); } // Create Default Elevation Dimension If Needed if (defaultElevationNeeded) { DimensionInfo di = new DimensionInfoImpl(); di.setEnabled(true); di.setPresentation(DimensionPresentation.LIST); di.setUnits("EPSG:5030"); di.setUnitSymbol("m"); di.setAttribute(elevationDimension.getStartAttribute()); ci.getMetadata().put(ResourceInfo.ELEVATION, di); } } } } finally { // WARNING: Disposing The Reader Causes The Catalog To Fail /*if (reader != null) { reader.dispose(); }*/ } } LOGGER.fine( " - [Remote Process Client - importLayer] The Importer has finished correctly for Spatial File:" + file.getAbsolutePath()); return task.getLayer(); } else { break; } } else { Thread.sleep(1500); } } } LOGGER.warning( " - [Remote Process Client - importLayer] The Importer has finished *BUT* did not returned any layer for Spatial File:" + file.getAbsolutePath()); return null; } }