/* File: DasServer.java * Copyright (C) 2002-2003 The University of Iowa * Created by: Jeremy Faden * Jessica Swanner * Edward E. West * * 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.client; import java.util.logging.Level; import org.das2.stream.StreamDescriptor; import org.das2.stream.StreamException; import org.das2.stream.DasStreamFormatException; import org.das2.util.URLBuddy; import org.das2.DasIOException; import org.das2.system.DasLogger; import org.das2.util.filesystem.FileSystem; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeModel; import java.io.*; import java.net.*; import java.util.*; import java.util.logging.Logger; import org.das2.DasException; import org.w3c.dom.Document; import org.w3c.dom.Element; /** Represents a remote Das 2.1 compliant server. * * Use the create() method to instantiate new Das 2 server objects. Each call to * create() will only allocate a new server instance if no server matching the given URL * has already been created. * * @author jbf * A DasServer is the object that holds the methods of a remote das server. * These include, for example, getLogo() which returns a graphical mnemonic * for the server, authenticate() and setPassword(). */ public class DasServer { private String sProto; private String host; private String path; private int port; @Deprecated private final HashMap keys; // Holds a list of all non-http auth das2 server keys //Probably dead code, let's see //private Key key; private static final Logger logger= DasLogger.getLogger( DasLogger.DATA_TRANSFER_LOG ); /* Holds the global list of Das2 Server objects */ private static HashMap instanceHashMap= new HashMap(); // @Deprecated // public static DasServer plasmaWaveGroup; // // @Deprecated // public static DasServer sarahandjeremy; // // static { // try { // plasmaWaveGroup= DasServer.create(new URL("http://www-pw.physics.uiowa.edu/das/das2Server")); // } catch ( java.net.MalformedURLException e ) { // throw new IllegalArgumentException(e); // } // try { // sarahandjeremy= DasServer.create(new URL("http://www.sarahandjeremy.net/das/dasServer.cgi")); // } catch ( java.net.MalformedURLException e ) { // throw new IllegalArgumentException(e); // } // } /** * return one DasServer to serve as an example. * @return */ public static DasServer createPlasmaWaveGroup() { try { return DasServer.create(new URL("http://planet.physics.uiowa.edu/das/das2Server")); } catch ( java.net.MalformedURLException e ) { throw new IllegalArgumentException(e); } } /** Class to represent know information about an item in a list of data sources. * @author cwp */ public static class DataSrcListItem { private boolean bDirectory; private String sName; private String sDesc; /** Create a data source item with a description string */ public DataSrcListItem(boolean bDirectory, String sName, String sDesc){ this.bDirectory = bDirectory; this.sName = sName; this.sDesc = sDesc; } public boolean isDirectory(){return bDirectory;} public boolean isDataSource(){return !bDirectory;} public String name(){return sName;} public String description(){return sDesc;} @Override public String toString(){return sName;} } private DasServer(String sProto, String host, String path) { String[] s= host.split(":"); if ( s.length>1 ) { this.port= Integer.parseInt(s[1]); host= s[0]; } else { port= -1; } this.sProto = sProto; this.host= host; this.path= path; this.keys= new HashMap(); } /** Provide the Das2 Server location. * Note that more than one Das2 server may be present on a single host web-site. * @return A URL string containing protocol, host and path information. */ public String getURL() { if ( port==-1 ) { return sProto+"://"+host+path; } else { return sProto+"://"+host+":"+port+path; } } /** Get a Das2 server instance. * * @param url A Das2 resource URL. Only the protocol, host, port and path information * are used. All other items, such as a GET query are ignored. * * @return If a server matching the given url's protocol, host and path has already * been created, that instance is returned, otherwise a new instance is created. */ public static DasServer create( URL url ) { String proto = url.getProtocol(); String host= url.getHost(); int port = url.getPort(); if ( port!=-1 ) { host+= ":"+port; } String key= proto+"://" + host + url.getPath(); if ( instanceHashMap.containsKey( key ) ) { logger.log( Level.FINE, "Using existing DasServer for {0}", url); return (DasServer) instanceHashMap.get( key ); } else { String path= url.getPath(); logger.log( Level.FINE, "Creating DasServer for {0}", url); DasServer result= new DasServer(proto, host, path); instanceHashMap.put(key,result); return result; } } /** Query the remote DasServer for it's id string. * For Das 2.1 servers this is handled by sending the GET query ?server=id. * * @return A string containing the id, or the empty string if the query failed */ public String getName() { String formData= "server=id"; InputStream in=null; try { URL server= new URL(sProto,host,port,path+"?"+formData); logger.log( Level.FINE, "connecting to {0}", server); URLConnection urlConnection = server.openConnection(); urlConnection.connect(); in= urlConnection.getInputStream(); String result= new String( read(in) ); logger.log( Level.FINE, "response={0}", result); return result; } catch (IOException e) { return ""; } finally { if ( in!=null ) try { in.close(); } catch ( IOException ex ) { logger.log( Level.WARNING, ex.toString(), ex ); } } } public ImageIcon getLogo() { String formData= "server=logo"; InputStream in=null; try { URL server= new URL(sProto,host,port,path+"?"+formData); logger.log( Level.FINE, "connecting to {0}", server); URLConnection urlConnection = server.openConnection(); urlConnection.connect(); in= urlConnection.getInputStream(); byte[] data= read(in); logger.log( Level.FINE, "response={0} bytes", data.length); return new ImageIcon(data); } catch (IOException e) { return new ImageIcon(); } finally { if ( in!=null ) try { in.close(); } catch ( IOException ex ) { logger.log( Level.WARNING, ex.toString(), ex ); } } } public TreeModel getDataSetListWithDiscovery() throws org.das2.DasException { return getDataSetList("?server=discovery"); } public TreeModel getDataSetList() throws org.das2.DasException { return getDataSetList("?server=list"); } protected TreeModel getDataSetList(String sSuffix) throws DasIOException{ InputStream in=null; try { URL server= new URL(sProto,host,port,path+sSuffix); logger.log( Level.FINE, "connecting to {0}", server); URLConnection conn = server.openConnection(); conn.setConnectTimeout(FileSystem.settings().getConnectTimeoutMs()); if(conn instanceof HttpURLConnection){ HttpURLConnection httpConn = (HttpURLConnection) conn; int nStatus = httpConn.getResponseCode(); if ( nStatus==301 ) { httpConn.disconnect(); //TODO: this is sloppy. The buffer needs to be emptied. String newUrl= conn.getHeaderField("Location"); if ( newUrl==null ) { throw new IllegalArgumentException("301 response but no new location"); } httpConn= (HttpURLConnection) new URL(newUrl).openConnection(); conn= httpConn; nStatus= ((HttpURLConnection) httpConn).getResponseCode(); //if ( conn.getHeaderField("Strict-Transport-Security")) } if(nStatus >= 400) // Just fail on 400's and 500's throw new java.io.IOException("Server returned HTTP response " + "code:" + nStatus + " for URL: " + server); } in= conn.getInputStream(); TreeModel result= createModel(in); logger.log( Level.FINE, "response->{0}", result); return result; } catch (IOException e) { throw new DasIOException( e.getMessage() ); } finally { if ( in!=null ) try { in.close(); } catch ( IOException ex ) { logger.log( Level.WARNING, ex.toString(), ex ); } } } /** * sort the DefaultMutableTreeNodes so the directories are listed before * the files. * @param tn */ private void sortDirectories( DefaultMutableTreeNode tn ) { DefaultMutableTreeNode[] children= new DefaultMutableTreeNode[tn.getChildCount()]; int ichild=0; for ( int i=0; i-1 ) { sDesc = line.substring(ipipe+1).trim(); if(sDesc.isEmpty()) sDesc = null; line = line.substring(0,ipipe).trim(); } DefaultMutableTreeNode current = root; StringTokenizer tokenizer = new StringTokenizer(line, "/"); token: while (tokenizer.hasMoreTokens()) { String tok = tokenizer.nextToken(); for (int index = 0; index < current.getChildCount(); index++) { String str = current.getChildAt(index).toString(); if (str.equals(tok)) { current = (DefaultMutableTreeNode)current.getChildAt(index); continue token; } } boolean bDir = tokenizer.hasMoreElements() ? true : line.endsWith("/"); DataSrcListItem dsItem = new DataSrcListItem(bDir, tok, sDesc); DefaultMutableTreeNode node = new DefaultMutableTreeNode(dsItem, bDir); current.add(node); current = node; } line = in.readLine(); } sortDirectories( root ); return model; } public StandardDataStreamSource getStandardDataStreamSource(URL url) { return new WebStandardDataStreamSource(this, url); } public StreamDescriptor getStreamDescriptor( URL dataSetID ) throws DasException { try { String dsdf = dataSetID.getQuery().split("&")[0]; URL url = new URL(sProto, host, port, path+"?server=dsdf&dataset=" + dsdf); logger.log( Level.FINE, "connecting to {0}", url); URLConnection connection = url.openConnection(); connection.connect(); String contentType = connection.getContentType(); String[] s1= contentType.split(";"); // dump charset info contentType= s1[0]; InputStream inStream = null; if(connection instanceof HttpURLConnection){ HttpURLConnection httpConn = (HttpURLConnection) connection; int nStatus = httpConn.getResponseCode(); if(nStatus >= 400) inStream = httpConn.getErrorStream(); } if(inStream == null) inStream = connection.getInputStream(); if (contentType.equalsIgnoreCase("text/plain") || contentType.equalsIgnoreCase("text/vnd.das2.das2stream") ) { PushbackReader reader = new PushbackReader(new InputStreamReader(inStream), 4); char[] four = new char[4]; int count= reader.read(four); if ( count!=4 ) throw new IllegalArgumentException("failed to read four characters"); if (new String(four).equals("[00]")) { logger.fine("response is a das2Stream"); if ( reader.skip(6)!=6 ) throw new IllegalArgumentException("expected to skip six characters"); Document header = StreamDescriptor.parseHeader(reader); Element root = header.getDocumentElement(); if (root.getTagName().equals("stream")) { return new StreamDescriptor(root); } else if ( root.getTagName().equals("exception") ) { logger.info("response is an exception"); String type= root.getAttribute("type"); StreamException se= new StreamException( "stream exception: "+type ); throw new DasException("stream exception: " + type, se); } else if (root.getTagName().equals("")) { throw new DasStreamFormatException(); } else { throw new DasStreamFormatException(); } } else { logger.info("response is a legacy descriptor"); reader.unread(four); BufferedReader in = new BufferedReader(reader); StreamDescriptor result = StreamDescriptor.createLegacyDescriptor(in); return result; } } else { BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); StringBuilder message = new StringBuilder(); for (String line = in.readLine(); line != null; line = in.readLine()) { message.append(line).append('\n'); } throw new IOException(message.toString()); } } catch ( MalformedURLException e ) { throw new DataSetDescriptorNotAvailableException("malformed URL"); } catch ( FileNotFoundException e ) { throw new DasServerNotFoundException( e.getMessage() ); } catch ( IOException e ) { throw new DasIOException(e.toString()); } } /** Handles key based authentication */ @Deprecated public Key authenticate( String user, String passCrypt) { try { Key result= null; String formData= "server=authenticator"; formData+= "&user="+URLBuddy.encodeUTF8(user); formData+= "&passwd="+URLBuddy.encodeUTF8(passCrypt); URL server= new URL(sProto,host,port,path+"?"+formData); logger.log( Level.FINE, "connecting to {0}", server); InputStream in= server.openStream(); BufferedInputStream bin= new BufferedInputStream(in); String serverResponse= readServerResponse(bin); String errTag= "error"; String keyTag= "key"; if ( serverResponse.substring(0,keyTag.length()+2).equals("<"+keyTag+">")) { int index= serverResponse.indexOf(""); String keyString= serverResponse.substring(keyTag.length()+2,index); result= new Key(keyString); } else { result= null; } return result; } catch (UnsupportedEncodingException uee) { throw new AssertionError("UTF-8 not supported"); } catch ( IOException e ) { return null; } } /** * returns a {@code List} of resource Id's available with this key * @param key the key * @return a list of the groups. * @deprecated this is not used. */ public List groups( Key key ) { try { String formData= "server=groups"; formData+= "&key="+URLBuddy.encodeUTF8(key.toString()); URL server= new URL(sProto,host,port,path+"?"+formData); logger.log( Level.FINE, "connecting to {0}", server); InputStream in= server.openStream(); BufferedInputStream bin= new BufferedInputStream(in); String serverResponse= readServerResponse(bin); String[] groups= serverResponse.split(","); ArrayList result= new ArrayList(); for ( int i=0; i")) { int index= serverResponse.indexOf(""); String errString= serverResponse.substring(errTag.length()+2,index); if (errString.equals("")) { throw new DasServerException("Bad User/Pass"); } } } catch (UnsupportedEncodingException uee) { throw new AssertionError("UTF-8 not supported"); } catch ( IOException e ) { throw new DasServerException("Failed Connection"); } } /** * * @param in * @return * @deprecated */ public String readServerResponse( BufferedInputStream in ) { // Read ..., leaving the InputStream immediately after // in.mark(Integer.MAX_VALUE); String das2Response; byte[] data = new byte[4096]; //int lastBytesRead = -1; //String s; int offset=0; try { int bytesRead= in.read(data,offset,4096-offset); String das2ResponseTag= "das2Response"; // beware das2ResponseTagLength=14 assumed below!!! if (bytesRead<(das2ResponseTag.length()+2)) { offset+= bytesRead; bytesRead= in.read(data,offset,4096-offset); } if ( new String(data,0,14,"UTF-8").equals("<"+das2ResponseTag+">")) { while ( !new String( data,0,offset,"UTF-8" ).contains("") && offset<4096 ) { offset+= bytesRead; bytesRead= in.read(data,offset,4096-offset); } int index= new String( data,0,offset,"UTF-8" ).indexOf(""); das2Response= new String(data,14,index-14); logger.log(Level.FINER, "das2Response={0}", das2Response); in.reset(); long n= das2Response.length() + 2 * das2ResponseTag.length() + 5; while ( n>0 ) { long k= in.skip( n ); n-= k; } } else { in.reset(); das2Response=""; } } catch ( IOException e ) { das2Response= ""; } logger.log( Level.FINE, "response={0}", das2Response); return das2Response; } /** * Utility function to handle reading data off the HTTP stream. Used by functions * such as getName and getLogo that don't expect to receive a Das2 Stream * * @param uin * @return * @throws IOException */ private byte[] read(InputStream uin) throws IOException { LinkedList list = new LinkedList(); byte[] data; int bytesRead=0; //int totalBytesRead=0; //BufferedInputStream in= new BufferedInputStream(uin,4096*2); InputStream in= uin; //long time = System.currentTimeMillis(); // fireReaderStarted(); //FileOutputStream out= new FileOutputStream("x."+time+".dat"); data = new byte[4096]; int lastBytesRead = -1; int offset=0; // if (requestor != null) { // requestor.totalByteCount(-1); // } bytesRead= in.read(data,offset,4096-offset); while (bytesRead != -1) { //int bytesSoFar = totalBytesRead; // fireReaderUpdate(bytesSoFar); // if (requestor != null) { // requestor.currentByteCount(bytesSoFar); // } offset+=bytesRead; lastBytesRead= offset; if (offset==4096) { list.addLast(data); data = new byte[4096]; offset=0; } //totalBytesRead+= bytesRead; bytesRead= in.read(data,offset,4096-offset); } if (lastBytesRead<4096) { list.addLast(data); } if (list.size()== 0) { return new byte[0]; } int dataLength = (list.size()-1)*4096 + lastBytesRead; data = new byte[dataLength]; Iterator iterator = list.iterator(); int i; for (i = 0; i < list.size()-1; i++) { System.arraycopy(iterator.next(), 0, data, i*4096, 4096); } System.arraycopy(iterator.next(), 0, data, i*4096, lastBytesRead); return data; } public String getProto() { return sProto; } public String getHost() { return host; } public int getPort() { // returns -1 if the port was not specified, which can be used with URL constructor return port; } public String getPath() { return path; } public URL getURL( String formData ) throws MalformedURLException { return new URL( sProto, host, port, path+"?"+formData ); } public Key getKey( String resource ) { synchronized (this) { if ( keys.get(resource)==null ) { Authenticator authenticator; authenticator= new Authenticator(this,resource); Key key1= authenticator.authenticate(); if ( key1!=null ) keys.put( resource, key1); } } return (Key)keys.get(resource); } public void setKey( Key key ) { logger.info("this key is ignored"); } @Override public String toString() { return this.getURL(); } }