/* File: DataSetDescriptor.java * Copyright (C) 2002-2003 The University of Iowa * * Created on October 22, 2003, 12:49 PM by __FULLNAME__ <__EMAIL__> * * This file is part of the das2 library. * * das2 is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.das2.dataset; import org.das2.datum.CacheTag; import java.awt.Graphics2D; import org.das2.components.propertyeditor.Displayable; import org.das2.datum.DatumRange; import org.das2.datum.Units; import org.das2.datum.Datum; import org.das2.client.DasServer; import org.das2.client.DataSetDescriptorNotAvailableException; import org.das2.client.StreamDataSetDescriptor; import org.das2.client.NoSuchDataSetException; import org.das2.stream.StreamDescriptor; import org.das2.DasIOException; import org.das2.DasApplication; import org.das2.DasException; import org.das2.util.monitor.NullProgressMonitor; import org.das2.util.monitor.ProgressMonitor; import org.das2.system.DasLogger; import org.das2.system.RequestProcessor; import org.das2.util.URLBuddy; import java.net.*; import java.util.*; import java.util.regex.*; import java.lang.reflect.*; import java.util.logging.Logger; import javax.swing.event.*; /** * DataSetDescriptors are a source from where datasets are produced. These uniquely identify a data set * that is parameteric. Typically, the parameter is time, so for example, there might be a DataSetDescriptor * for "discharge of the Iowa River measured at Iowa City." Clients of the class get * DataSets from the DataSetDescriptor via the getDataSet( Start, End, Resolution ) method. So for * example, you might ask what is the discharge from June 1 to August 31st, 2005, at a resolution of * 1 day. Presently, it's implicit that this means to give bin averages of the data. * *
DataSetDescriptors are identified with a URL-like string: * {@code http://www-pw.physics.uiowa.edu/das/das2Server?das2_1/cluster/wbd/r_wbd_dsn_cfd&spacecraft%3Dc1%26antenna%3DEy} *
* *The protocol of the string indicates how the DataSetDescriptor is to be constructed, and presently * there are: *
* http a das2Server provides the specification of the datasetdescriptor. * class refers to a loadable java class that is an instanceof DataSetDescriptor and * has the method newDataSetDescriptor( Map params ) throws DasException ** @author jbf */ public abstract class DataSetDescriptor implements Displayable { protected Map properties = new HashMap(); private boolean defaultCaching = true; private DataSetCache dataSetCache; private String dataSetID; private EventListenerList listenerList; protected DataSetDescriptor(final String dataSetID) { dataSetCache = DasApplication.getDefaultApplication().getDataSetCache(); this.dataSetID = dataSetID; } protected DataSetDescriptor() { this(""); } private static final Logger logger = DasLogger.getLogger(DasLogger.GRAPHICS_LOG); /** * getDataSetImpl implements the getDataSet for this DataSetDescriptor implementation. The * getDataSet call of the abstract DataSetDescriptor uses this routine to satisfy requests and * fill its cache. This caching may be disabled via setDefaultCaching. To satisfy the request, * a DataSet should be returned with an x tag range that contains start and end, with a * resolution finer than that requested. * * @param start beginning of range for the request. * @param end end of the range for the request. * @param resolution the resolution requirement for the reqeust.
null
may be used to request the finest resolution available or intrinic resolution.
*/
protected abstract DataSet getDataSetImpl(Datum start, Datum end, Datum resolution, ProgressMonitor monitor) throws DasException;
/**
* @return the x units of the DataSetDescriptor that parameterize the data. This is used to identify dataSet requests.
*/
public abstract Units getXUnits();
/**
* Requests that a dataSet be loaded, and that the dataSet be returned via a DataSetUpdate event.
* The @param lockObject is an object that is dependent on the load, for example, the DasCanvas,
* and will be passed in to the request processor. If the dataSet is available in interactive time,
* then the dataSetUpdate may be fired on the same thread as the request is made.
*/
public void requestDataSet(final Datum start, final Datum end, final Datum resolution,
final ProgressMonitor monitor, Object lockObject) {
Runnable request = new Runnable() {
public void run() {
logger.info("requestDataSet: " + start + " " + end + " " + resolution);
try {
DataSet ds = getDataSet(start, end, resolution, monitor);
if (ds == null) {
throw new NoDataInIntervalException(new DatumRange(start, end).toString());
}
DataSetUpdateEvent dsue = new DataSetUpdateEvent((Object)DataSetDescriptor.this, DataSetAdapter.create(ds) );
dsue.setMonitor(monitor);
fireDataSetUpdateEvent(dsue);
} catch (DasException e) {
DataSetUpdateEvent dsue = new DataSetUpdateEvent((Object)DataSetDescriptor.this, e);
dsue.setMonitor(monitor);
fireDataSetUpdateEvent(dsue);
}
}
public String toString() {
return "loadDataSet " + new DatumRange(start, end);
}
};
logger.info("submit data request");
CacheTag tag = new CacheTag(start, end, resolution);
if (dataSetCache.haveStored(this, tag)) {
request.run();
} else {
RequestProcessor.invokeLater(request, lockObject);
}
}
/**
* Request the dataset, and the dataset is returned only to the listener.
*
* @param lockObject object that is waiting for the result of this load, used to block other tasks which use that object.
*/
public void requestDataSet(final Datum start, final Datum end, final Datum resolution,
final ProgressMonitor monitor, Object lockObject, final DataSetUpdateListener listener) {
if (lockObject == null) {
lockObject = listener;
}
if (this instanceof ConstantDataSetDescriptor) {
try {
DataSet ds = getDataSet(null, null, null, null);
DataSetUpdateEvent dsue = new DataSetUpdateEvent((Object)this, DataSetAdapter.create(ds) );
dsue.setMonitor(monitor);
} catch (DasException e) {
DataSetUpdateEvent dsue = new DataSetUpdateEvent((Object)DataSetDescriptor.this, e);
dsue.setMonitor(monitor);
listener.dataSetUpdated(dsue);
}
} else {
Runnable request = new Runnable() {
public void run() {
logger.info("request data from dsd: " + start + " " + end + " " + resolution);
try {
DataSet ds = getDataSet(start, end, resolution, monitor);
DataSetUpdateEvent dsue = new DataSetUpdateEvent((Object)DataSetDescriptor.this, DataSetAdapter.create(ds) );
dsue.setMonitor(monitor);
listener.dataSetUpdated(dsue);
} catch (DasException e) {
DataSetUpdateEvent dsue = new DataSetUpdateEvent((Object)DataSetDescriptor.this, e);
dsue.setMonitor(monitor);
listener.dataSetUpdated(dsue);
}
}
public String toString() {
return "loadDataSet " + new DatumRange(start, end);
}
};
RequestProcessor.invokeLater(request, lockObject);
}
}
/**
* Retrieve the dataset for this interval and resolution. The contract for this function is that
* identical start,end,resolution parameters will yield an identical dataSet, except for when an
* DataSetUpdate has been fired in the meantime.
*
* null for the data resolution indicates that the data should be returned at its "intrinsic resolution"
* if such a resolution exists.
*/
public DataSet getDataSet(Datum start, Datum end, Datum resolution, ProgressMonitor monitor) throws DasException {
if (monitor == null) {
monitor = new NullProgressMonitor();
}
CacheTag tag = null;
if (defaultCaching) {
tag = new CacheTag(start, end, resolution);
DasLogger.getLogger(DasLogger.DATA_TRANSFER_LOG).info("getDataSet " + this + " " + tag);
}
if (defaultCaching && dataSetCache.haveStored(this, tag)) {
return dataSetCache.retrieve(this, tag);
} else {
try {
DataSet ds = getDataSetImpl(start, end, resolution, monitor);
if (ds != null) {
if (ds.getProperty("cacheTag") != null) {
tag = (CacheTag) ds.getProperty("cacheTag");
}
if (defaultCaching) {
dataSetCache.store(this, tag, ds);
}
}
return ds;
} catch (DasException e) {
throw e;
} finally {
monitor.finished();
}
}
}
/**
* clear any state that's developed, in particular any data caches. Note
* this currently deletes all cached datasets, regardless of the DataSetDescriptor
* that produced them.
*/
public void reset() {
dataSetCache.reset();
}
/**
* defaultCaching means that the abstract DataSetDescriptor is allowed to handle
* repeat getDataSet calls by returning a cached dataset. If a dataSetUpdate event
* is thrown, the defaultCache is reset.
*
* Use caution when using this. Note that caching may only be turned off
* with this call.
*/
public void setDefaultCaching(boolean value) {
if (value == false) {
defaultCaching = value;
}
}
public void addDataSetUpdateListener(DataSetUpdateListener listener) {
if (listenerList == null) {
listenerList = new EventListenerList();
}
listenerList.add(DataSetUpdateListener.class, listener);
}
public void removeDataSetUpdateListener(DataSetUpdateListener listener) {
if (listenerList == null) {
listenerList = new EventListenerList();
}
listenerList.remove(DataSetUpdateListener.class, listener);
}
protected void fireDataSetUpdateEvent(DataSetUpdateEvent event) {
if (listenerList == null) {
return;
}
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == DataSetUpdateListener.class) {
((DataSetUpdateListener) listeners[i + 1]).dataSetUpdated(event);
}
}
}
/**
* @return the string that uniquely identifies this dataset.
*/
public String getDataSetID() {
return this.dataSetID;
}
private static final Pattern CLASS_ID = Pattern.compile("class:([a-zA-Z0-9_\\.]+)(?:\\?(.*))?");
private static final Pattern NAME_VALUE = Pattern.compile("([_0-9a-zA-Z%+.-]+)=([_0-9a-zA-Z%+.-]+)");
/**
* creates a DataSetDescriptor for the given identification string. The identification
* string is a URL-like string, for example
* {@code http://www-pw.physics.uiowa.edu/das/das2Server?das2_1/cluster/wbd/r_wbd_dsn_cfd&spacecraft%3Dc1%26antenna%3DEy}
* The protocol of the string indicates how the DataSetDescriptor is to be constructed, and presently
* there are:
** http a das2Server provides the specification of the DataSetDescriptor, and a * StreamDataSetDescriptor is created. * class refers to a loadable java class that is an instanceof DataSetDescriptor and * has the method newDataSetDescriptor( Map params ) ** Note that DataSetDescriptors are stateless, the same DataSetDescriptor object * may be returned to multiple clients. * @param dataSetID the URL-like identifier. * @return the DataSetDescriptor for the id. * @throws org.das2.DasException */ public static DataSetDescriptor create(final String dataSetID) throws DasException { java.util.regex.Matcher classMatcher = CLASS_ID.matcher(dataSetID); DataSetDescriptor result; if (classMatcher.matches()) { result = createFromClassName(dataSetID, classMatcher); } else { try { result = createFromServerAddress(new URL(dataSetID)); //result = DasServer.createDataSetDescriptor(new URL(dataSetID)); } catch (MalformedURLException mue) { throw new DasIOException(mue.getMessage()); } } result.dataSetID = dataSetID; return result; } private static DataSetDescriptor createFromServerAddress(final URL url) throws DasException { DasServer server = DasServer.create(url); StreamDescriptor sd = server.getStreamDescriptor(url); return new StreamDataSetDescriptor(sd, server.getStandardDataStreamSource(url)); } private static DataSetDescriptor createFromClassName(final String dataSetID, final Matcher matcher) throws DasException { try { String className = matcher.group(1); String argString = matcher.group(2); Map argMap = argString == null ? Collections.EMPTY_MAP : URLBuddy.parseQueryString(argString); Class dsdClass = Class.forName(className); Method method = dsdClass.getMethod("newDataSetDescriptor", new Class[]{java.util.Map.class}); if (!Modifier.isStatic(method.getModifiers())) { throw new NoSuchDataSetException("newDataSetDescriptor must be static"); } return (DataSetDescriptor) method.invoke(null, new Object[]{argMap}); } catch (ClassNotFoundException cnfe) { DataSetDescriptorNotAvailableException dsdnae = new DataSetDescriptorNotAvailableException(cnfe.getMessage()); dsdnae.initCause(cnfe); throw dsdnae; } catch (NoSuchMethodException nsme) { DataSetDescriptorNotAvailableException dsdnae = new DataSetDescriptorNotAvailableException(nsme.getMessage()); dsdnae.initCause(nsme); throw dsdnae; } catch (InvocationTargetException ite) { DataSetDescriptorNotAvailableException dsdnae = new DataSetDescriptorNotAvailableException(ite.getTargetException().getMessage()); dsdnae.initCause(ite.getTargetException()); throw dsdnae; } catch (IllegalAccessException iae) { DataSetDescriptorNotAvailableException dsdnae = new DataSetDescriptorNotAvailableException(iae.getMessage()); dsdnae.initCause(iae); throw dsdnae; } } protected void setProperties(Map properties) { this.properties.putAll(properties); } /** * Returns the value of the property with the specified name * * @param name The name of the property requested * @return The value of the requested property as an Object */ public Object getProperty(String name) { return properties.get(name); } public javax.swing.Icon getListIcon() { return null; } public void drawListIcon( Graphics2D g, int x, int y ) { } public String getListLabel() { return this.dataSetID; } /** * @return the DataSetCache object used to store cached copies of the * DataSets created by this object. */ public DataSetCache getDataSetCache() { return this.dataSetCache; } }