All Downloads are FREE. Search and download functionalities are using the official Maven repository.

thredds.catalog.InvDatasetFeatureCollection Maven / Gradle / Ivy

Go to download

The NetCDF-Java Library is a Java interface to NetCDF files, as well as to many other types of scientific data formats.

The newest version!
/*
 * Copyright (c) 1998 - 2010. University Corporation for Atmospheric Research/Unidata
 * Portions of this software were developed by the Unidata Program at the
 * University Corporation for Atmospheric Research.
 *
 * Access and use of this software shall impose the following obligations
 * and understandings on the user. The user is granted the right, without
 * any fee or cost, to use, copy, modify, alter, enhance and distribute
 * this software, and any derivative works thereof, and its supporting
 * documentation for any purpose whatsoever, provided that this entire
 * notice appears in all copies of the software, derivative works and
 * supporting documentation.  Further, UCAR requests that the user credit
 * UCAR/Unidata in any publications that result from the use of this
 * software or in any product that includes this software. The names UCAR
 * and/or Unidata, however, may not be used in any advertising or publicity
 * to endorse or promote any products or commercial entity unless specific
 * written permission is obtained from UCAR/Unidata. The user also
 * understands that UCAR/Unidata is not obligated to provide the user with
 * any support, consulting, training or assistance of any kind with regard
 * to the use, operation and performance of this software nor to provide
 * the user with any updates, revisions, new versions or "bug fixes."
 *
 * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package thredds.catalog;

import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
import org.slf4j.Logger;
import thredds.crawlabledataset.CrawlableDataset;
import thredds.crawlabledataset.CrawlableDatasetFilter;
import thredds.featurecollection.FeatureCollectionConfig;
import thredds.featurecollection.FeatureCollectionType;
import thredds.inventory.*;
import thredds.inventory.MCollection;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dt.GridDataset;
import ucar.nc2.ft.FeatureDataset;
import ucar.nc2.time.CalendarDateRange;
import ucar.nc2.units.DateRange;
import ucar.nc2.util.log.LoggerFactory;
import ucar.nc2.util.log.LoggerFactoryImpl;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.regex.Pattern;

/**
 * Abstract superclass for Feature Collection InvDatasets.
 * This is a InvCatalogRef subclass. So the reference is placed in the parent, but
 * the catalog itself isnt constructed until the following call from DataRootHandler.makeDynamicCatalog():
 * match.dataRoot.featCollection.makeCatalog(match.remaining, path, baseURI);
 * 

* The InvDatasetFeatureCollection object is created once and held in the DataRootHandler's collection * of DataRoots. * * @author caron * @since Mar 3, 2010 */ @ThreadSafe public abstract class InvDatasetFeatureCollection extends InvCatalogRef implements CollectionUpdateListener { static protected final String LATEST_DATASET_CATALOG = "latest.xml"; static protected final String LATEST_SERVICE = "latest"; static protected final String VARIABLES = "?metadata=variableMap"; static protected final String FILES = "files"; static protected final String Virtual_Services = "VirtualServices"; // exclude HTTPServer static private String catalogServletName = "/catalog"; // LOOK static protected String context = "/thredds"; // LOOK static private String cdmrFeatureServiceUrlPath = "/cdmrFeature"; // LOOK static private LoggerFactory loggerFactory = new LoggerFactoryImpl(); static private org.slf4j.Logger classLogger = org.slf4j.LoggerFactory.getLogger(InvDatasetFeatureCollection.class); static public void setContext(String c) { context = c; } static public void setCatalogServletName(String catServletName) { catalogServletName = catServletName; } static protected String buildCatalogServiceHref(String path) { return context + (catalogServletName == null ? "" : catalogServletName) + "/" + path + "/catalog.xml"; } static public void setCdmrFeatureServiceUrlPath(String urlPath) { cdmrFeatureServiceUrlPath = urlPath; } static public void setLoggerFactory(LoggerFactory fac) { loggerFactory = fac; } static private InvService makeCdmrFeatureService() { return new InvService("cdmrFeature", "cdmrFeature", context + cdmrFeatureServiceUrlPath, null, null); } static public InvDatasetFeatureCollection factory(InvDatasetImpl parent, String name, String path, FeatureCollectionType fcType, FeatureCollectionConfig config) { InvDatasetFeatureCollection result; if (fcType == FeatureCollectionType.FMRC) result = new InvDatasetFcFmrc(parent, name, path, fcType, config); else if (fcType == FeatureCollectionType.GRIB1 || fcType == FeatureCollectionType.GRIB2) { // use reflection to decouple from grib.jar try { Class c = InvDatasetFeatureCollection.class.getClassLoader().loadClass("thredds.catalog.InvDatasetFcGrib"); // public InvDatasetFcGrib(InvDatasetImpl parent, String name, String path, FeatureType featureType, FeatureCollectionConfig config) { Constructor ctor = c.getConstructor(InvDatasetImpl.class, String.class, String.class, FeatureCollectionType.class, FeatureCollectionConfig.class); result = (InvDatasetFeatureCollection) ctor.newInstance(parent, name, path, fcType, config); } catch (Throwable e) { classLogger.error("Failed to open " + name + " path=" + path, e); return null; } } else { result = new InvDatasetFcPoint(parent, name, path, fcType, config); } if (result != null) { result.finishConstruction(); // stuff that shouldnt be done in a constructor } return result; } ///////////////////////////////////////////////////////////////////////////// // heres how we manage state changes in a thread-safe way protected class State { // catalog metadata protected ThreddsMetadata.Variables vars; protected ThreddsMetadata.GeospatialCoverage coverage; protected CalendarDateRange dateRange; protected InvDatasetImpl top; // top dataset protected long lastInvChange; // last time dataset inventory was changed protected long lastProtoChange; // last time proto dataset was changed protected State(State from) { if (from != null) { this.vars = from.vars; this.coverage = from.coverage; this.dateRange = from.dateRange; this.lastProtoChange = from.lastProtoChange; this.top = from.top; this.lastInvChange = from.lastInvChange; } } protected State copy() { // allow override return new State(this); } } ///////////////////////////////////////////////////////////////////////////// // not changed after first call protected InvService orgService, virtualService; protected InvService cdmrService; // LOOK why do we need to specify this seperately ?? protected org.slf4j.Logger logger; // from the config catalog protected final String path; protected final FeatureCollectionType fcType; protected final FeatureCollectionConfig config; protected String topDirectory; protected MCollection datasetCollection; // defines the collection of datasets in this feature collection, actually final protected String collectionName; @GuardedBy("lock") protected State state; @GuardedBy("lock") protected boolean first = true; protected final Object lock = new Object(); protected InvDatasetFeatureCollection(InvDatasetImpl parent, String name, String path, FeatureCollectionType fcType, FeatureCollectionConfig config) { super(parent, name, buildCatalogServiceHref(path)); this.path = path; this.fcType = fcType; this.config = config; this.getLocalMetadataInheritable().setDataType(fcType.getFeatureType()); this.collectionName = CollectionAbstract.cleanName(config.name != null ? config.name : name); config.name = collectionName; this.logger = loggerFactory.getLogger("fc." + collectionName); // seperate log file for each feature collection (!!) this.logger.info("FeatureCollection added = {}", getConfig()); } protected void makeCollection() { Formatter errlog = new Formatter(); if (config.spec.startsWith(MFileCollectionManager.CATALOG)) { // LOOK CHANGE THIS datasetCollection = new CollectionManagerCatalog(config.name, config.spec, null, errlog); } else { datasetCollection = new MFileCollectionManager(config, errlog, this.logger); } topDirectory = datasetCollection.getRoot(); String errs = errlog.toString(); if (errs.length() > 0) logger.warn("MFileCollectionManager parse error = {} ", errs); } // stuff that shouldnt be done in a constructor - eg dont let 'this' escape // LOOK maybe not best design to start tasks from here // LOOK we want to get notified of events, but no longer initiate changes. protected void finishConstruction() { CollectionUpdater.INSTANCE.scheduleTasks(config, this, logger); } public String getCollectionName() { return collectionName; } @Override // DatasetCollectionManager was changed asynchronously public void sendEvent(CollectionUpdateType type) { update(type); /* if (event == CollectionUpdateListener.TriggerType.updateNocheck) else if (event == CollectionUpdateListener.TriggerType.update) update(CollectionManager.Force.always); else if (event == CollectionUpdateListener.TriggerType.resetProto) updateProto(); */ } /** * update the proto dataset being used. * called by CollectionUpdater via handleCollectionEvent, so in a quartz scheduler thread */ abstract protected void updateProto(); // localState is synched, may be directly changed abstract protected void updateCollection(State localState, CollectionUpdateType force); abstract protected void makeDatasetTop(State localState); // this allows us to put warnings into the catalogInit.log @Override boolean check(StringBuilder out, boolean show) { boolean isValid = true; // no longer need default service for FC 3/11/14 /* if (getServiceDefault() == null) { out.append("**Warning: Dataset (").append(getFullName()).append("): has no default service\n"); isValid = false; } */ return isValid && super.check(out, show); } //////////////////////////////////////////////////////////////////////////////////////////// protected String getCatalogHref(String what) { return buildCatalogServiceHref(path + "/" + what); } // call this first time a request comes in protected void firstInit() { this.orgService = getServiceDefault(); if (this.orgService == null) { this.orgService = makeServiceDefault(); } this.virtualService = makeServiceVirtual(this.orgService); this.cdmrService = makeCdmrFeatureService(); // WTF ?? } /** * A request has come in, check that the state has been initialized. * this is called from the request thread. * * @return a copy of the State */ protected State checkState() { State localState; synchronized (lock) { if (first) { firstInit(); updateCollection(state, config.updateConfig.updateType); makeDatasetTop(state); first = false; } localState = state.copy(); } return localState; } /** * Collection was changed, update internal objects. * called by CollectionUpdater, trigger via handleCollectionEvent, so in a quartz scheduler thread * * @param force test : update index if anything changed or nocheck - use index if it exists */ protected void update(CollectionUpdateType force) { // this may be called from a background thread State localState; synchronized (lock) { if (first) { state = checkState(); state.lastInvChange = System.currentTimeMillis(); return; } // do the update in a local object localState = state.copy(); } updateCollection(localState, force); makeDatasetTop(localState); localState.lastInvChange = System.currentTimeMillis(); // switch to live synchronized (lock) { state = localState; } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// public String getPath() { return path; } public String getLatestFileName() { if (config.gribConfig.latestNamer != null) { return config.gribConfig.latestNamer; } else { return "Latest "+name+" File"; } } public String getTopDirectoryLocation() { return topDirectory; } public FeatureCollectionConfig getConfig() { return config; } public MCollection getDatasetCollectionManager() { return datasetCollection; } public Logger getLogger() { return logger; } @Override public java.util.List getDatasets() { // probably not used State localState; try { localState = checkState(); } catch (Exception e) { logger.error("Error in checkState", e); return new ArrayList<>(0); } List tops = new ArrayList<>(1); tops.add(localState.top); return tops; } /////////////////////////////////////////////////////////////////////////////////////////////////// protected InvService makeServiceDefault() { // LOOK need (thredds.server.config.AllowableService) InvService result = new InvService("Default", ServiceType.COMPOUND.toString(), null, null, null); result.addService(InvService.opendap); result.addService(InvService.fileServer); result.addService(InvService.wms); result.addService(InvService.wcs); result.addService(InvService.ncss); result.addService(InvService.cdmremote); result.addService(InvService.ncml); result.addService(InvService.uddc); result.addService(InvService.iso); return result; } protected InvService makeServiceVirtual(InvService org) { if (org.getServiceType() != ServiceType.COMPOUND) return org; InvService result = new InvService(Virtual_Services, ServiceType.COMPOUND.toString(), null, null, null); for (InvService service : org.getServices()) { if (service.getServiceType() != ServiceType.HTTPServer) { result.addService(service); } } return result; } /** * Get one of the catalogs contained in this collection, * called by DataRootHandler.makeDynamicCatalog() * * @param match match.remaining * @param orgPath the path for the request. * @param catURI the base URI for the catalog to be made, used to resolve relative URLs. * @return containing catalog */ abstract public InvCatalogImpl makeCatalog(String match, String orgPath, URI catURI); /** * Make the containing catalog of this feature collection * "http://server:port/thredds/catalog/path/catalog.xml" * * @param catURI base URI of the request * @param localState current state to use * @return the top FMRC catalog * @throws java.io.IOException on I/O error * @throws java.net.URISyntaxException if path is misformed */ protected InvCatalogImpl makeCatalogTop(URI catURI, State localState) throws IOException, URISyntaxException { InvCatalogImpl parentCatalog = (InvCatalogImpl) getParentCatalog(); InvCatalogImpl mainCatalog = new InvCatalogImpl(getName(), parentCatalog.getVersion(), catURI); mainCatalog.addDataset(localState.top); mainCatalog.addService(InvService.latest); // in case its needed mainCatalog.addService(virtualService); // top.getLocalMetadataInheritable().setServiceName(virtualService.getName()); //?? mainCatalog.finish(); return mainCatalog; } // this catalog lists the individual files comprising the collection. protected InvCatalogImpl makeCatalogFiles(URI catURI, State localState, List filenames, boolean addLatest) throws IOException { //String collectionName = gc.getName(); InvCatalogImpl parent = (InvCatalogImpl) getParentCatalog(); //URI myURI = baseURI.resolve(getCatalogHref(collectionName)); //URI myURI = baseURI.resolve(getCatalogHref(FILES)); InvCatalogImpl result = new InvCatalogImpl(getFullName(), parent.getVersion(), catURI); InvDatasetImpl top = new InvDatasetImpl(this); top.setParent(null); top.transferMetadata((InvDatasetImpl) this.getParent(), true); // make all inherited metadata local top.setName(FILES); // add Variables, GeospatialCoverage, TimeCoverage ThreddsMetadata tmi = top.getLocalMetadataInheritable(); if (localState.coverage != null) tmi.setGeospatialCoverage(localState.coverage); tmi.setTimeCoverage((DateRange) null); // LOOK result.addDataset(top); // services need to be local result.addService(orgService); top.getLocalMetadataInheritable().setServiceName(orgService.getName()); //String id = getID(); //if (id == null) id = getPath(); if (addLatest) { InvDatasetImpl ds = new InvDatasetImpl(this, getLatestFileName()); ds.setUrlPath(LATEST_DATASET_CATALOG); // ds.setID(getPath() + "/" + FILES + "/" + LATEST_DATASET_CATALOG); ds.setServiceName(LATEST_SERVICE); ds.finish(); top.addDataset(ds); result.addService(InvService.latest); } //sort copy of files List sortedFilenames = new ArrayList(filenames); Collections.sort(sortedFilenames, String.CASE_INSENSITIVE_ORDER); // if not increasing (i.e. we WANT newest file listed first), reverse sort if (this.config.gribConfig != null && !this.config.gribConfig.filesSortIncreasing) { Collections.reverse(sortedFilenames); } for (String f : sortedFilenames) { if (!f.startsWith(topDirectory)) logger.warn("File {} doesnt start with topDir {}", f, topDirectory); String fname = f.substring(topDirectory.length() + 1); InvDatasetImpl ds = new InvDatasetImpl(this, fname); String lpath = getPath() + "/" + FILES + "/" + fname; ds.setUrlPath(lpath); ds.setID(lpath); ds.tmi.addVariableMapLink(makeMetadataLink(lpath, VARIABLES)); File file = new File(f); ds.tm.setDataSize(file.length()); ds.finish(); top.addDataset(ds); } result.finish(); return result; } protected String makeMetadataLink(String datasetName, String metadata) { return context + "/metadata/" + datasetName + metadata; } // called by DataRootHandler.makeDynamicCatalog() public InvCatalogImpl makeLatest(String matchPath, String reqPath, URI catURI) throws IOException { checkState(); InvCatalogImpl parent = (InvCatalogImpl) getParentCatalog(); InvCatalogImpl result = new InvCatalogImpl(getFullName(), parent.getVersion(), catURI); InvDatasetImpl top = new InvDatasetImpl(this); top.setParent(null); top.transferMetadata((InvDatasetImpl) this.getParent(), true); // make all inherited metadata local top.setName(getLatestFileName()); // add Variables, GeospatialCoverage, TimeCoverage // ThreddsMetadata tmi = top.getLocalMetadataInheritable(); //if (localState.gc != null) tmi.setGeospatialCoverage(localState.gc); //if (localState.dateRange != null) tmi.setTimeCoverage(localState.dateRange); result.addDataset(top); // services need to be local // result.addService(InvService.latest); if (orgService != null) { result.addService(orgService); top.getLocalMetadataInheritable().setServiceName(orgService.getName()); } MFile mfile = datasetCollection.getLatestFile(); // LOOK - assumes dcm is up to date if (mfile == null) return null; String mpath = mfile.getPath(); if (!mpath.startsWith(topDirectory)) logger.warn("File {} doesnt start with topDir {}", mpath, topDirectory); String fname = mpath.substring(topDirectory.length() + 1); String path = FILES + "/" + fname; top.setUrlPath(this.path + "/" + path); top.setID(this.path + "/" + path); top.tmi.addVariableMapLink(makeMetadataLink(this.path + "/" + path, VARIABLES)); top.tm.setDataSize(mfile.getLength()); result.finish(); return result; } ///////////////////////////////////////////////////////////////////////// /** * Get the associated Grid Dataset, if any. called by DatasetHandler.openGridDataset() * * @param matchPath match.remaining * @return Grid Dataset, or null if n/a * @throws IOException on error */ public ucar.nc2.dt.grid.GridDataset getGridDataset(String matchPath) throws IOException { int pos = matchPath.indexOf('/'); String type = (pos > -1) ? matchPath.substring(0, pos) : matchPath; String name = (pos > -1) ? matchPath.substring(pos + 1) : ""; // this assumes that these are files. also might be remote datasets from a catalog if (type.equals(FILES)) { if (topDirectory == null) return null; String filename = topDirectory + (topDirectory.endsWith("/") ? "" : "/") + name; NetcdfDataset ncd = NetcdfDataset.acquireDataset(null, filename, null, -1, null, null); // no enhancement return new ucar.nc2.dt.grid.GridDataset(ncd); } return null; } abstract public FeatureDataset getFeatureDataset(); /////////////////////////////////////////////////////////////////////////////// // handle individual files /** * Get the dataset named by the path. * called by DatasetHandler.getNetcdfFile() * * @param matchPath remaining path from match * @return requested dataset * @throws IOException if read error */ public NetcdfDataset getNetcdfDataset(String matchPath) throws IOException { int pos = matchPath.indexOf('/'); String type = (pos > -1) ? matchPath.substring(0, pos) : matchPath; String name = (pos > -1) ? matchPath.substring(pos + 1) : ""; // this assumes that these are files. also might be remote datasets from a catalog if (type.equals(FILES)) { if (topDirectory == null) return null; String filename = new StringBuilder(topDirectory) .append(topDirectory.endsWith("/") ? "" : "/") .append(name).toString(); return NetcdfDataset.acquireDataset(null, filename, null, -1, null, null); // no enhancement } GridDataset gds = getGridDataset(matchPath); // LOOK cant be right return (gds == null) ? null : (NetcdfDataset) gds.getNetcdfFile(); } // called by DataRootHandler.getCrawlableDatasetAsFile() // may have to remove the extra "files" from the path // this says that a File URL has to be topDirectory + [FILES/ +] + match.remaining public File getFile(String remaining) { if (null == topDirectory) return null; int pos = remaining.indexOf(FILES); StringBuilder fname = new StringBuilder(topDirectory); if (!topDirectory.endsWith("/")) fname.append("/"); fname.append((pos > -1) ? remaining.substring(pos + FILES.length() + 1) : remaining); return new File(fname.toString()); } // specialized filter handles olderThan and/or filename pattern matching // for InvDatasetScan static class ScanFilter implements CrawlableDatasetFilter { private final Pattern p; private final long olderThan; public ScanFilter(Pattern p, long olderThan) { this.p = p; this.olderThan = olderThan; } @Override public boolean accept(CrawlableDataset dataset) { if (dataset.isCollection()) return true; if (p != null) { java.util.regex.Matcher matcher = p.matcher(dataset.getName()); if (!matcher.matches()) return false; } if (olderThan > 0) { Date lastModDate = dataset.lastModified(); if (lastModDate != null) { long now = System.currentTimeMillis(); if (now - lastModDate.getTime() <= olderThan) return false; } } return true; } @Override public Object getConfigObject() { return null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy