/* * AggregatingDataSourceFactory.java * * Created on October 25, 2007, 11:02 AM * * To change this template, choose Tools | Template Manager * and open the template in the editor. */ package org.autoplot.aggregator; import java.io.File; import java.io.FileNotFoundException; import org.das2.datum.DatumRangeUtil; import java.util.logging.Level; import java.util.logging.Logger; import org.das2.util.monitor.NullProgressMonitor; import org.das2.fsm.FileStorageModel; import org.das2.util.filesystem.FileSystem; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.das2.datum.DatumRange; import org.das2.util.LoggerManager; import org.das2.util.monitor.ProgressMonitor; import org.autoplot.datasource.CompletionContext; import org.autoplot.datasource.DataSetURI; import org.autoplot.datasource.DataSource; import org.autoplot.datasource.DataSourceFactory; import org.autoplot.datasource.DefaultTimeSeriesBrowse; import org.autoplot.datasource.URISplit; import org.autoplot.datasource.capability.TimeSeriesBrowse; import org.das2.util.filesystem.LocalFileSystem; /** * ftp://cdaweb.gsfc.nasa.gov/pub/data/noaa/noaa14/$Y/noaa14_meped1min_sem_$Y$m$d_v01.cdf?timerange=2000-01-01 * @author jbf */ public class AggregatingDataSourceFactory implements DataSourceFactory { public static final String PROB_NO_TIMERANGE_PROVIDED = "no timerange provided"; public static final String PROB_PARSE_ERROR_IN_TIMERANGE = "parse error in timeRange"; private static final Logger logger= LoggerManager.getLogger("apdss.agg"); private DataSourceFactory delegateFactory=null; /** Creates a new instance of AggregatingDataSourceFactory */ public AggregatingDataSourceFactory() { } @Override public DataSource getDataSource(URI uri) throws Exception { String suri= DataSetURI.fromUri(uri); if ( suri.contains("&timerange") && !suri.contains("?") ) { throw new IllegalArgumentException("data URI contains &timerange but no question mark."); } if ( delegateFactory==null ) { delegateFactory= AggregatingDataSourceFactory.getDelegateDataSourceFactory( suri ); } if ( delegateFactory==null ) { throw new IllegalArgumentException("Unable to identify data source for "+suri); } AggregatingDataSource ads = new AggregatingDataSource(uri,delegateFactory); String surl = DataSetURI.fromUri( uri ); FileStorageModel fsm = getFileStorageModel(surl); ads.setFsm(fsm); URISplit split = URISplit.parse(surl); Map parms = URISplit.parseParams(split.params); String stimeRange= (String) parms.get("timerange"); if ( stimeRange!=null ) { stimeRange= stimeRange.replaceAll("\\+", " " ); ads.setViewRange(DatumRangeUtil.parseTimeRange(stimeRange)); parms.remove("timerange"); } if (parms.size() > 0) { ads.setParams(URISplit.formatParams(parms)); } return ads; } /** * return the index of the agg part of the uri. Like * FileStorageModel.splitIndex(surl), but also treats * as $x. * @param surl the URI as a string. * @return the index of the first part of the aggregation, which will * be one more than the position of the static part's last slash. */ public static int splitIndex(String surl) { // See also org/autoplot/pngwalk/WalkUtil.java splitIndex... surl= surl.replaceAll("\\*","\\$x"); return FileStorageModel.splitIndex(surl); } /** * return the FileStorageModel for the URI. * @param suri eg. file:/tmp/foo/$Y/$Y$m.dat * @return the FileStorageModel, eg. for $Y/$Y$m.dat. * @throws IOException * @see #splitIndex(java.lang.String) which splits the static part from the agg part. */ public static FileStorageModel getFileStorageModel(String suri) throws IOException { URISplit split= URISplit.parse(suri); String surl= split.surl; // support cases where resource URI is not yet valid. int i = surl.indexOf('?'); String sansArgs = i == -1 ? surl : surl.substring(0, i); i = splitIndex(sansArgs); FileSystem fs; if ( i==-1 ) i= sansArgs.lastIndexOf("/"); fs = FileSystem.create( DataSetURI.toUri(sansArgs.substring(0, i))); if ( sansArgs.charAt(i)=='/' ) i=i+1; // kludgy String spec= sansArgs.substring(i).replaceAll("\\%", "\\$"); FileStorageModel fsm = FileStorageModel.create(fs, spec ); return fsm; } /** * get a representative file, using one from the timerange in the surl if available. This will help * users to work around problems where the first folder (2012) doesn't contain a representative (2012/20120212.dat) * but the second (2013) does. * @param fsm * @param surl * @return * @throws IOException */ private static String getRepresentativeFile( FileStorageModel fsm, String surl, ProgressMonitor mon ) throws IOException { if ( mon==null ) { mon= new NullProgressMonitor(); } //System.err.println("getRepr "+Integer.toHexString( mon.hashCode() ) ); try { URISplit split = URISplit.parse(surl); Map params= URISplit.parseParams(split.params); String delegateFile; String stimeRange= params.get( "timerange" ); if ( stimeRange!=null ) { DatumRange tdr= null; try { tdr= DatumRangeUtil.parseTimeRange(stimeRange); } catch ( ParseException ex ) { logger.finer("unable to parse timerange, just use default delegate"); } if ( tdr!=null ) { String[] names= fsm.getBestNamesFor( tdr, mon.getSubtaskMonitor("get best names") ); if ( names.length>0 ) { delegateFile= names[0]; } else { delegateFile= fsm.getRepresentativeFile( mon.getSubtaskMonitor("get delegate") ); } } else { delegateFile= fsm.getRepresentativeFile( mon.getSubtaskMonitor("get delegate") ); } } else { delegateFile= fsm.getRepresentativeFile( mon.getSubtaskMonitor("get delegate") ); } return delegateFile; } finally { mon.finished(); } } private static CompletionContext getDelegateDataSourceCompletionContext(CompletionContext cc) throws IOException { String surl = cc.surl; int carotPos = cc.surlpos; int urlLen = 0; //this is the position as we parse and process surl. surl= surl.replaceAll("%25","%"); FileStorageModel fsm = getFileStorageModel(surl); String delegateFile= getRepresentativeFile( fsm, surl, null ); if (delegateFile == null) { throw new FileNotFoundException("unable to find any files matching\n"+surl); } URISplit split = URISplit.parse(surl); String encodedDelegateFile= delegateFile.replaceAll(":","%3A"); String delegateFfile; if ( fsm.getFileSystem() instanceof LocalFileSystem ) { delegateFfile= new File( ((LocalFileSystem)fsm.getFileSystem()).getLocalRoot(), URISplit.uriEncode(encodedDelegateFile) ).toString(); } else { delegateFfile= fsm.getFileSystem().getRootURI().resolve(URISplit.uriEncode(encodedDelegateFile)).toString(); } urlLen += delegateFfile.length(); carotPos -= urlLen - delegateFfile.length(); split.file = delegateFfile; int i = surl.lastIndexOf("timerange=", cc.surlpos); if (i != -1) { int i1 = surl.indexOf('&', i); carotPos -= (i1 - i); } Map parms = URISplit.parseParams(split.params); parms.remove("timerange"); split.params = URISplit.formatParams(parms); String delegateUrl = URISplit.format(split); CompletionContext delegatecc = new CompletionContext(); delegatecc.surl = delegateUrl; delegatecc.surlpos = carotPos; delegatecc.context = cc.context; String decodedDelegateFile= delegateFfile.replaceAll("%3A",":"); //delegatecc.resource= new URL( delegateFfile ); delegatecc.resourceURI = DataSetURI.toUri(decodedDelegateFile); return delegatecc; } /** * @param suri the aggregation, containing the template and the timerange parameter. * @param mon a progress monitor. * @return the URI for each granule. * @throws java.io.IOException * @throws IllegalArgumentException if it is not able to find any data files. */ public static String getDelegateDataSourceFactoryUri(String suri, ProgressMonitor mon) throws IOException, IllegalArgumentException { URISplit split= URISplit.parse(suri); Map parms = URISplit.parseParams(split.params); String timeRange= ((String) parms.remove("timerange")); if ( timeRange!=null ) timeRange= timeRange.replaceAll("\\+", " "); parms.remove("reduce"); split.params = URISplit.formatParams(parms); FileStorageModel fsm = getFileStorageModel( DataSetURI.fromUri(split.resourceUri) ); String file= null; if ( timeRange!=null && !timeRange.equals("") ) { try { DatumRange timeRangeDatum= DatumRangeUtil.parseTimeRange(timeRange); String[] names = fsm.getBestNamesFor(timeRangeDatum,new NullProgressMonitor()); FileSystem fs= fsm.getFileSystem(); for (String name : names) { // look for a file which is not empty. if (fs.getFileObject(name).getSize() > 0) { file = name; break; } } if ( file==null ){ if ( names.length>0 ) { file= names[0]; } } } catch (ParseException ex) { logger.log(Level.SEVERE, ex.getMessage(), ex); } } if ( file==null ) { file = getRepresentativeFile( fsm, suri, mon ); } if (file == null) { throw new IllegalArgumentException( "unable to find any files in "+fsm ); } if ( fsm.getFileSystem() instanceof LocalFileSystem ) { split.resourceUri= fsm.getFileSystem().getFileObject(file).getFile().toURI(); } else { split.resourceUri= fsm.getFileSystem().getRootURI().resolve(file.replaceAll(":","%3A")); } String scompUrl = DataSetURI.fromUri( split.resourceUri ); if (split.params.length() > 0) { scompUrl += "?" + split.params; } split.file= DataSetURI.fromUri( split.resourceUri ); //split.path= // URISplit.format ignores this. split.surl= scompUrl; return URISplit.format(split); } public static DataSourceFactory getDelegateDataSourceFactory(String surl) throws IOException, IllegalArgumentException, URISyntaxException { String delegateSurl = getDelegateDataSourceFactoryUri(surl, new NullProgressMonitor() ); URISplit split= URISplit.parse(surl); URISplit delegateSplit= URISplit.parse(delegateSurl); delegateSplit.vapScheme= split.vapScheme; // TODO: verify this URI uri= DataSetURI.toUri( URISplit.format(delegateSplit) ); return DataSetURI.getDataSourceFactory( uri, new NullProgressMonitor()); } @Override public List getCompletions(CompletionContext cc,org.das2.util.monitor.ProgressMonitor mon) throws Exception { if ( delegateFactory==null ) { delegateFactory= getDelegateDataSourceFactory(cc.surl); } DataSourceFactory f = delegateFactory; List result = new ArrayList<>(); CompletionContext delegatecc = getDelegateDataSourceCompletionContext(cc); List delegateCompletions = f.getCompletions(delegatecc,mon); result.addAll(delegateCompletions); if (cc.context == CompletionContext.CONTEXT_PARAMETER_NAME) { result.add(new CompletionContext( CompletionContext.CONTEXT_PARAMETER_NAME, "timerange=" )); } else if (cc.context == CompletionContext.CONTEXT_PARAMETER_VALUE) { String paramName = CompletionContext.get(CompletionContext.CONTEXT_PARAMETER_NAME, cc); if (paramName.equals("timerange")) { result.add(new CompletionContext(CompletionContext.CONTEXT_PARAMETER_VALUE, "")); } } else { } return result; } protected static boolean hasTimeFields( String surl ) { if ( surl.contains("%Y") || surl.contains("%25Y" ) ) { logger.warning("URIs should no longer contain percents (%s)."); return true; } int ipercy = surl.lastIndexOf("%Y"); if (ipercy == -1) ipercy = surl.lastIndexOf("$Y"); if (ipercy == -1) ipercy = surl.lastIndexOf("$y"); if (ipercy == -1) ipercy = surl.lastIndexOf("$(o"); if (ipercy == -1) ipercy = surl.lastIndexOf("$(periodic"); return ipercy != -1; } @Override public boolean reject( String surl, List problems, ProgressMonitor mon) { URISplit split = URISplit.parse(surl); Map map = URISplit.parseParams(split.params); try { if ( hasTimeFields(surl) && DefaultTimeSeriesBrowse.reject( map, problems ) ) { return true; } String delegateSurl = getDelegateDataSourceFactoryUri(surl,mon); String avail= (String) map.get("avail"); if ( avail==null || !avail.equals("T") ) { if ( delegateFactory==null ) { delegateFactory= getDelegateDataSourceFactory(surl); if ( delegateFactory==null ) { return true; } } boolean delegateRejects= delegateFactory.reject( delegateSurl, problems, mon ); if ( delegateRejects && problems.size()==1 && problems.get(0).equals(TimeSeriesBrowse.PROB_NO_TIMERANGE_PROVIDED) ) { delegateRejects= false; } return delegateRejects; } else { return false; } } catch (URISyntaxException | IOException e) { problems.add(e.getMessage()); logger.log( Level.SEVERE, surl, e ); return false; } catch (IllegalArgumentException e) { problems.add(e.getMessage()); logger.log( Level.SEVERE, surl, e ); return true; } } public void setDelegateDataSourceFactory(DataSourceFactory delegateFactory) { this.delegateFactory= delegateFactory; } @Override public T getCapability(Class clazz) { if ( clazz==TimeSeriesBrowse.class ) { return (T) new DefaultTimeSeriesBrowse(); } return null; } @Override public boolean supportsDiscovery() { return false; } }