From 021054e428566fa0a81c0ebada457b8e50511151 Mon Sep 17 00:00:00 2001
From: gmantele <gmantele@ari.uni-heidelberg.de>
Date: Fri, 23 Jan 2015 15:54:23 +0100
Subject: [PATCH] [UWS,TAP] Fix UPLOAD bug (the last line of the table to
 upload was ignored). Fix an improbable NullPointerException in the
 MultipartParser.

---
 src/tap/data/VOTableIterator.java            | 129 ++++++++++---------
 src/tap/parameters/DALIUpload.java           |   2 +-
 src/uws/service/request/MultipartParser.java |   2 +
 3 files changed, 72 insertions(+), 61 deletions(-)

diff --git a/src/tap/data/VOTableIterator.java b/src/tap/data/VOTableIterator.java
index c7bd03b..e60d5a8 100644
--- a/src/tap/data/VOTableIterator.java
+++ b/src/tap/data/VOTableIterator.java
@@ -51,17 +51,17 @@ public class VOTableIterator implements TableIterator {
 		 * <p><i>Note: this may be NULL after the metadata has been read if an error occurred while performing the conversion.
 		 * In this case, metaError contains this error.</> */
 		private TAPColumn[] meta = null;
-		
+
 		/** The error which happened while converting the StarTable metadata into TAP metadata. */
 		private DataReadException metaError = null;
-		
+
 		/** The last accepted row. */
 		private Object[] pendingRow = null;
-		
+
 		/** Flag meaning that the end of the stream has been reached
 		 * OR if the VOTable reading should be stopped before reading more rows. */
 		private boolean endReached = false;
-		
+
 		/**
 		 * <p>Stop nicely reading the VOTable.</p>
 		 * 
@@ -74,17 +74,17 @@ public class VOTableIterator implements TableIterator {
 			endReached = true;
 			notifyAll();
 		}
-		
+
 		@Override
-		public synchronized void acceptMetadata(final StarTable metaTable) throws TableFormatException {
+		public synchronized void acceptMetadata(final StarTable metaTable) throws TableFormatException{
 			try{
 				// Convert the StartTable metadata into TAP metadata:
 				meta = extractColMeta(metaTable);
-				
+
 			}catch(DataReadException dre){
 				// Save the error ; this error will be throw when a call to getMetadata() will be done:
 				metaError = dre;
-				
+
 			}finally{
 				// Free all waiting threads:
 				notifyAll();
@@ -92,18 +92,18 @@ public class VOTableIterator implements TableIterator {
 		}
 
 		@Override
-		public synchronized void acceptRow(final Object[] row) throws IOException {
+		public synchronized void acceptRow(final Object[] row) throws IOException{
 			try{
 				// Wait until the last accepted row has been consumed: 
 				while(!endReached && pendingRow != null)
 					wait();
-				
+
 				/* If the end has been reached, this is not normal
 				 * (because endRows() is always called after acceptRow()...so, it means the iteration has been aborted before the end)
 				 * and so the stream reading should be interrupted: */
 				if (endReached)
 					throw new IOException("Streaming aborted!");
-				
+
 				// Otherwise, keep the given row:
 				pendingRow = row;
 
@@ -113,13 +113,13 @@ public class VOTableIterator implements TableIterator {
 				 * ...which should then mean that the end of the stream has been reached. */
 				if (pendingRow == null)
 					endReached = true;
-				
+
 			}catch(InterruptedException ie){
 				/* If the thread has been interrupted, set this TableSink in a state similar to
 				 * when the end of the stream has been reached: */
 				pendingRow = null;
 				endReached = true;
-				
+
 			}finally{
 				// In all cases, all waiting threads must be freed:
 				notifyAll();
@@ -127,15 +127,23 @@ public class VOTableIterator implements TableIterator {
 		}
 
 		@Override
-		public synchronized void endRows() throws IOException {
-			// No more rows are available:
-			pendingRow = null;
-			// Set the END flag:
-			endReached = true;
-			// Notify all waiting threads that the end has been reached:
-			notifyAll();
+		public synchronized void endRows() throws IOException{
+			try{
+				// Wait until the last accepted row has been consumed:
+				while(!endReached && pendingRow != null)
+					wait();
+			}catch(InterruptedException ie){
+				/* Nothing to do in particular ; the end of the stream will be set anyway. */
+			}finally{
+				// No more rows are available:
+				pendingRow = null;
+				// Set the END flag:
+				endReached = true;
+				// Notify all waiting threads that the end has been reached:
+				notifyAll();
+			}
 		}
-		
+
 		/**
 		 * <p>Get the metadata found in the VOTable.</p>
 		 * 
@@ -154,14 +162,14 @@ public class VOTableIterator implements TableIterator {
 				// Wait until metadata are available, or if an error has occurred while accepting them:
 				while(metaError == null && meta == null)
 					wait();
-				
+
 				// If there was an error while interpreting the accepted metadata, throw it:
 				if (metaError != null)
 					throw metaError;
-				
+
 				// Otherwise, just return the metadata:
 				return meta;
-				
+
 			}catch(InterruptedException ie){
 				/* If the thread has been interrupted, set this TableSink in a state similar to
 				 * when the end of the stream has been reached: */
@@ -169,13 +177,13 @@ public class VOTableIterator implements TableIterator {
 				/* Return the metadata ;
 				 * NULL will be returned if the interruption has occurred before the real reading of the VOTable metadata: */
 				return meta;
-				
+
 			}finally{
 				// In all cases, the waiting threads must be freed:
 				notifyAll();
 			}
 		}
-		
+
 		/**
 		 * <p>Get the last accepted row.</p>
 		 * 
@@ -186,35 +194,35 @@ public class VOTableIterator implements TableIterator {
 		 * 
 		 * @return
 		 */
-		public synchronized Object[] getRow() {
+		public synchronized Object[] getRow(){
 			try{
 				// Wait until a row has been accepted or the end has been reached:
 				while(!endReached && pendingRow == null)
 					wait();
-				
+
 				// If there is no more rows, just return NULL (meaning for the called "end of stream"):
-				if (endReached)
+				if (endReached && pendingRow == null)
 					return null;
-				
+
 				/* Otherwise, reset pendingRow to NULL in order to enable the reading of the next row,
 				 * and finally return the last accepted row: */
 				Object[] row = pendingRow;
 				pendingRow = null;
 				return row;
-				
+
 			}catch(InterruptedException ie){
 				/* If the thread has been interrupted, set this TableSink in a state similar to
 				 * when the end of the stream has been reached: */
 				endReached = true;
 				// Return NULL, meaning the end of the stream has been reached:
 				return null;
-				
-			}finally {
+
+			}finally{
 				// In all cases, the waiting threads must be freed:
 				notifyAll();
 			}
 		}
-		
+
 		/**
 		 * Extract an array of {@link TAPColumn} objects. Each corresponds to one of the columns listed in the given table,
 		 * and so corresponds to the metadata of a column. 
@@ -307,9 +315,9 @@ public class VOTableIterator implements TableIterator {
 			// Build the VOTable type:
 			return new VotType(votdatatype, arraysize, xtype);
 		}
-		
+
 	}
-	
+
 	/** Stream containing the VOTable on which this {@link TableIterator} is iterating. */
 	protected final InputStream input;
 	/** The StarTable consumer which is used to iterate on each row. */
@@ -319,14 +327,14 @@ public class VOTableIterator implements TableIterator {
 	protected boolean iterationStarted = false;
 	/** Indicate whether the last row has already been reached. */
 	protected boolean endReached = false;
-	
+
 	/** The last read row. Column iteration is done on this array. */
 	protected Object[] row;
 	/** Index of the last read column (=0 just after {@link #nextRow()} and before {@link #nextCol()}, ={@link #nbColumns} after the last column has been read). */
 	protected int indCol = -1;
 	/** Number of columns available according to the metadata. */
 	protected int nbCol = 0;
-	
+
 	/**
 	 * Build a TableIterator able to read rows and columns inside the given VOTable input stream.
 	 * 
@@ -340,7 +348,7 @@ public class VOTableIterator implements TableIterator {
 		if (input == null)
 			throw new NullPointerException("Missing VOTable document input stream over which to iterate!");
 		this.input = input;
-		
+
 		try{
 
 			// Set the VOTable builder/interpreter:
@@ -348,19 +356,20 @@ public class VOTableIterator implements TableIterator {
 
 			// Build the TableSink to use:
 			sink = new StreamVOTableSink();
-			
+
 			// Initiate the stream process:
-			Thread streamThread = new Thread() {
-                public void run() {
-                    try{
-            			tb.streamStarTable(input, sink, null);
-                    }catch(IOException e) {
-                    	if (e.getMessage() != null && !e.getMessage().equals("Reading interrupted!"))
-                    		e.printStackTrace();
-                    }
-                }
-            };
-            streamThread.start();
+			Thread streamThread = new Thread(){
+				@Override
+				public void run(){
+					try{
+						tb.streamStarTable(input, sink, null);
+					}catch(IOException e){
+						if (e.getMessage() != null && !e.getMessage().equals("Reading interrupted!"))
+							e.printStackTrace();
+					}
+				}
+			};
+			streamThread.start();
 
 		}catch(Exception ex){
 			throw new DataReadException("Unable to parse/read the given VOTable input stream!", ex);
@@ -368,33 +377,33 @@ public class VOTableIterator implements TableIterator {
 	}
 
 	@Override
-	public TAPColumn[] getMetadata() throws DataReadException {
+	public TAPColumn[] getMetadata() throws DataReadException{
 		return sink.getMeta();
 	}
 
 	@Override
-	public boolean nextRow() throws DataReadException {
+	public boolean nextRow() throws DataReadException{
 		// If no more rows, return false directly:
 		if (endReached)
 			return false;
-		
+
 		// Fetch the row:
 		row = sink.getRow();
-		
+
 		// Reset the column iteration:
 		if (!iterationStarted){
 			iterationStarted = true;
 			nbCol = sink.getMeta().length;
 		}
 		indCol = 0;
-		
+
 		// Tells whether there is more rows or not:
 		endReached = (row == null);
 		return !endReached;
 	}
 
 	@Override
-	public boolean hasNextCol() throws IllegalStateException, DataReadException {
+	public boolean hasNextCol() throws IllegalStateException, DataReadException{
 		// Check the read state:
 		checkReadState();
 
@@ -403,7 +412,7 @@ public class VOTableIterator implements TableIterator {
 	}
 
 	@Override
-	public Object nextCol() throws NoSuchElementException, IllegalStateException, DataReadException {
+	public Object nextCol() throws NoSuchElementException, IllegalStateException, DataReadException{
 		// Check the read state and ensure there is still at least one column to read:
 		if (!hasNextCol())
 			throw new NoSuchElementException("No more field to read!");
@@ -413,7 +422,7 @@ public class VOTableIterator implements TableIterator {
 	}
 
 	@Override
-	public DBType getColType() throws IllegalStateException, DataReadException {
+	public DBType getColType() throws IllegalStateException, DataReadException{
 		// Basically check the read state (for rows iteration):
 		checkReadState();
 
@@ -428,7 +437,7 @@ public class VOTableIterator implements TableIterator {
 	}
 
 	@Override
-	public void close() throws DataReadException {
+	public void close() throws DataReadException{
 		endReached = true;
 		sink.stop();
 		// input.close(); // in case sink.stop() is not enough to stop the VOTable reading!
diff --git a/src/tap/parameters/DALIUpload.java b/src/tap/parameters/DALIUpload.java
index 71694b8..e55c74a 100644
--- a/src/tap/parameters/DALIUpload.java
+++ b/src/tap/parameters/DALIUpload.java
@@ -512,7 +512,7 @@ public class DALIUpload {
 
 		// Check the label:
 		if (!parts[0].matches("[a-zA-Z][a-zA-Z0-9_]*"))
-			throw new TAPException("Wrong uploaded item name syntax: \"" + parts[0] + "\"! An uploaded item must have a label with the syntax: [a-zA-Z][a-zA-Z0-9_]*.", UWSException.BAD_REQUEST);
+			throw new TAPException("Wrong uploaded item name syntax: \"" + parts[0] + "\"! An uploaded item must have a label respecting the 'regular_identifier' production of ADQL 2.0 (regular expression: [a-zA-Z][a-zA-Z0-9_]*).", UWSException.BAD_REQUEST);
 		// Check the URI:
 		else if (!parts[1].matches("[a-zA-Z][a-zA-Z0-9\\+\\.\\-]*:.+"))
 			throw new TAPException("Bad URI syntax: \"" + parts[1] + "\"! A URI must start with: \"<scheme>:\", where <scheme>=\"[a-zA-Z][a-zA-Z0-9+.-]*\".", UWSException.BAD_REQUEST);
diff --git a/src/uws/service/request/MultipartParser.java b/src/uws/service/request/MultipartParser.java
index 5d4fc6a..a6760ac 100644
--- a/src/uws/service/request/MultipartParser.java
+++ b/src/uws/service/request/MultipartParser.java
@@ -166,6 +166,8 @@ public class MultipartParser implements RequestParser {
 				throw new UWSException(UWSException.BAD_REQUEST, "Uploads are not allowed by this service!");
 			while(e.hasMoreElements()){
 				param = e.nextElement();
+				if (multipart.getFile(param) == null)
+					continue;
 
 				/*
 				 * TODO !!!POSSIBLE ISSUE!!!
-- 
GitLab