From 9eaf1026c424d072f86beb66ed95df83992b98ac Mon Sep 17 00:00:00 2001
From: gmantele <gmantele@ari.uni-heidelberg.de>
Date: Mon, 9 Feb 2015 18:49:55 +0100
Subject: [PATCH] [TAP] Add ADQL geometry restriction inside the configuration
 file.

---
 src/tap/config/DefaultServiceConnection.java  | 55 ++++++++++++++++-
 src/tap/config/TAPConfiguration.java          |  4 ++
 src/tap/config/tap_configuration_file.html    | 17 ++++++
 .../config/TestDefaultServiceConnection.java  | 60 ++++++++++++++++++-
 4 files changed, 132 insertions(+), 4 deletions(-)

diff --git a/src/tap/config/DefaultServiceConnection.java b/src/tap/config/DefaultServiceConnection.java
index 7dfcf6f..c7baae8 100644
--- a/src/tap/config/DefaultServiceConnection.java
+++ b/src/tap/config/DefaultServiceConnection.java
@@ -13,6 +13,7 @@ import static tap.config.TAPConfiguration.KEY_DEFAULT_UPLOAD_LIMIT;
 import static tap.config.TAPConfiguration.KEY_DIRECTORY_PER_USER;
 import static tap.config.TAPConfiguration.KEY_FILE_MANAGER;
 import static tap.config.TAPConfiguration.KEY_FILE_ROOT_PATH;
+import static tap.config.TAPConfiguration.KEY_GEOMETRIES;
 import static tap.config.TAPConfiguration.KEY_GROUP_USER_DIRECTORIES;
 import static tap.config.TAPConfiguration.KEY_MAX_ASYNC_JOBS;
 import static tap.config.TAPConfiguration.KEY_MAX_EXECUTION_DURATION;
@@ -31,6 +32,7 @@ import static tap.config.TAPConfiguration.VALUE_CSV;
 import static tap.config.TAPConfiguration.VALUE_DB;
 import static tap.config.TAPConfiguration.VALUE_JSON;
 import static tap.config.TAPConfiguration.VALUE_LOCAL;
+import static tap.config.TAPConfiguration.VALUE_NONE;
 import static tap.config.TAPConfiguration.VALUE_SV;
 import static tap.config.TAPConfiguration.VALUE_TSV;
 import static tap.config.TAPConfiguration.VALUE_XML;
@@ -97,7 +99,10 @@ public final class DefaultServiceConnection implements ServiceConnection {
 
 	private UserIdentifier userIdentifier = null;
 
-	private final Collection<FunctionDef> udfs = new ArrayList<FunctionDef>(0);
+	private ArrayList<String> geometries = null;
+	private final String GEOMETRY_REGEXP = "(AREA|BOX|CENTROID|CIRCLE|CONTAINS|DISTANCE|COORD1|COORD2|COORDSYS|INTERSECTS|POINT|POLYGON|REGION)";
+
+	private Collection<FunctionDef> udfs = new ArrayList<FunctionDef>(0);
 
 	public DefaultServiceConnection(final Properties tapConfig) throws NullPointerException, TAPException, UWSException{
 		// 1. INITIALIZE THE FILE MANAGER:
@@ -139,7 +144,10 @@ public final class DefaultServiceConnection implements ServiceConnection {
 		// 8. SET A USER IDENTIFIER:
 		initUserIdentifier(tapConfig);
 
-		// 9. MAKE THE SERVICE AVAILABLE:
+		// 9. CONFIGURE ADQL:
+		initADQLGeometries(tapConfig);
+
+		// 10. MAKE THE SERVICE AVAILABLE:
 		setAvailable(true, "TAP service available.");
 	}
 
@@ -445,6 +453,47 @@ public final class DefaultServiceConnection implements ServiceConnection {
 		}
 	}
 
+	private void initADQLGeometries(final Properties tapConfig) throws TAPException{
+		// Get the property value:
+		String propValue = getProperty(tapConfig, KEY_GEOMETRIES);
+
+		// NO VALUE => ALL FCT ALLOWED!
+		if (propValue == null)
+			geometries = null;
+
+		// "NONE" => ALL FCT FORBIDDEN (= none of these functions are allowed)!
+		else if (propValue.equalsIgnoreCase(VALUE_NONE))
+			geometries = new ArrayList<String>(0);
+
+		// OTHERWISE, JUST THE ALLOWED ONE ARE LISTED:
+		else{
+			// split all the list items:
+			String[] items = propValue.split(",");
+			if (items.length > 0){
+				geometries = new ArrayList<String>(items.length);
+				for(String item : items){
+					item = item.trim();
+					// empty item => ignored
+					if (item.length() <= 0)
+						continue;
+					// if it is a name of known ADQL geometrical function, add it to the list:
+					else if (item.toUpperCase().matches(GEOMETRY_REGEXP))
+						geometries.add(item.toUpperCase());
+					// "NONE" is not allowed inside a list => error!
+					else if (item.toUpperCase().equals(VALUE_NONE))
+						throw new TAPException("The special value \"" + VALUE_NONE + "\" can not be used inside a list! It MUST be used in replacement of a whole list to specify that no value is allowed.");
+					// unknown value => error!
+					else
+						throw new TAPException("Unknown ADQL geometrical function: \"" + item + "\"!");
+				}
+				// if finally no item has been specified, consider it as "all functions allowed":
+				if (geometries.size() == 0)
+					geometries = null;
+			}else
+				geometries = null;
+		}
+	}
+
 	@Override
 	public String getProviderName(){
 		return providerName;
@@ -668,7 +717,7 @@ public final class DefaultServiceConnection implements ServiceConnection {
 
 	@Override
 	public Collection<String> getGeometries(){
-		return null;	// ALL GEOMETRIES ALLOWED
+		return geometries;
 	}
 
 	@Override
diff --git a/src/tap/config/TAPConfiguration.java b/src/tap/config/TAPConfiguration.java
index 8cb44b6..860873e 100644
--- a/src/tap/config/TAPConfiguration.java
+++ b/src/tap/config/TAPConfiguration.java
@@ -86,6 +86,10 @@ public final class TAPConfiguration {
 	/* USER IDENTIFICATION */
 	public final static String KEY_USER_IDENTIFIER = "user_identifier";
 
+	/* ADQL RESTRICTIONS */
+	public final static String KEY_GEOMETRIES = "geometries";
+	public final static String VALUE_NONE = "NONE";
+
 	/**
 	 * <p>Read the asked property from the given Properties object.</p>
 	 * <ul>
diff --git a/src/tap/config/tap_configuration_file.html b/src/tap/config/tap_configuration_file.html
index 429d2fd..7573f5e 100644
--- a/src/tap/config/tap_configuration_file.html
+++ b/src/tap/config/tap_configuration_file.html
@@ -454,6 +454,23 @@
 				</td>
 				<td>{apackage.FooUserIdentifier}</td>
 			</tr>
+			
+			<tr><td colspan="5">ADQL restrictions</td></tr>
+			<tr>
+				<td class="done">geometries</td>
+				<td></td>
+				<td>text</td>
+				<td>
+					<p>Comma-separated list of all allowed geometries.</p>
+					<p>
+						Each item of the list must be the name (whatever is the case) of an ADQL geometrical function (e.g. INTERSECTS, COORDSYS, POINT) to allow.
+						If the list is empty (no item), all functions are allowed. And if the special value <em>NONE</em> is given, no ADQL function will be allowed.
+					</p>
+					<p><em>By default, all ADQL geometrical functions are allowed.</em></p>
+				</td>
+				<td><ul><li>ΓΈ <em>(default)</em></li><li>NONE</li><li>CONTAINS, intersects, Point, Box, CIRCLE</li></ul></td>
+			</tr>
+			
 		</table>
 	</body>
 </html>
\ No newline at end of file
diff --git a/test/tap/config/TestDefaultServiceConnection.java b/test/tap/config/TestDefaultServiceConnection.java
index 54c3e95..4124ce7 100644
--- a/test/tap/config/TestDefaultServiceConnection.java
+++ b/test/tap/config/TestDefaultServiceConnection.java
@@ -8,6 +8,7 @@ import static org.junit.Assert.fail;
 import static tap.config.TAPConfiguration.DEFAULT_MAX_ASYNC_JOBS;
 import static tap.config.TAPConfiguration.KEY_DEFAULT_OUTPUT_LIMIT;
 import static tap.config.TAPConfiguration.KEY_FILE_MANAGER;
+import static tap.config.TAPConfiguration.KEY_GEOMETRIES;
 import static tap.config.TAPConfiguration.KEY_MAX_ASYNC_JOBS;
 import static tap.config.TAPConfiguration.KEY_MAX_OUTPUT_LIMIT;
 import static tap.config.TAPConfiguration.KEY_METADATA;
@@ -18,12 +19,14 @@ import static tap.config.TAPConfiguration.VALUE_CSV;
 import static tap.config.TAPConfiguration.VALUE_DB;
 import static tap.config.TAPConfiguration.VALUE_JSON;
 import static tap.config.TAPConfiguration.VALUE_LOCAL;
+import static tap.config.TAPConfiguration.VALUE_NONE;
 import static tap.config.TAPConfiguration.VALUE_SV;
 import static tap.config.TAPConfiguration.VALUE_TSV;
 import static tap.config.TAPConfiguration.VALUE_XML;
 
 import java.io.File;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Map;
 import java.util.Properties;
 
@@ -52,7 +55,8 @@ public class TestDefaultServiceConnection {
 			badSVFormat2Prop, unknownFormatProp, maxAsyncProp,
 			negativeMaxAsyncProp, notIntMaxAsyncProp, defaultOutputLimitProp,
 			maxOutputLimitProp, bothOutputLimitGoodProp,
-			bothOutputLimitBadProp, userIdentProp, notClassPathUserIdentProp;
+			bothOutputLimitBadProp, userIdentProp, notClassPathUserIdentProp,
+			geometriesProp, noneGeomProp, noneInsideGeomProp, unknownGeomProp;
 
 	@Before
 	public void setUp() throws Exception{
@@ -126,6 +130,18 @@ public class TestDefaultServiceConnection {
 
 		notClassPathUserIdentProp = (Properties)validProp.clone();
 		notClassPathUserIdentProp.setProperty(KEY_USER_IDENTIFIER, "foo");
+
+		geometriesProp = (Properties)validProp.clone();
+		geometriesProp.setProperty(KEY_GEOMETRIES, "point, CIRCle  ,	cONTAins,intersECTS");
+
+		noneGeomProp = (Properties)validProp.clone();
+		noneGeomProp.setProperty(KEY_GEOMETRIES, VALUE_NONE);
+
+		noneInsideGeomProp = (Properties)validProp.clone();
+		noneInsideGeomProp.setProperty(KEY_GEOMETRIES, "POINT, Box, none, circle");
+
+		unknownGeomProp = (Properties)validProp.clone();
+		unknownGeomProp.setProperty(KEY_GEOMETRIES, "POINT, Contains, foo, circle,Polygon");
 	}
 
 	/**
@@ -167,6 +183,7 @@ public class TestDefaultServiceConnection {
 			assertTrue(connection.getRetentionPeriod()[0] <= connection.getRetentionPeriod()[1]);
 			assertTrue(connection.getExecutionDuration()[0] <= connection.getExecutionDuration()[1]);
 			assertNull(connection.getUserIdentifier());
+			assertNull(connection.getGeometries());
 
 			// finally, save metadata in an XML file for the other tests:
 			writer = new PrintWriter(new File(XML_FILE));
@@ -195,6 +212,7 @@ public class TestDefaultServiceConnection {
 			assertTrue(connection.getRetentionPeriod()[0] <= connection.getRetentionPeriod()[1]);
 			assertTrue(connection.getExecutionDuration()[0] <= connection.getExecutionDuration()[1]);
 			assertNull(connection.getUserIdentifier());
+			assertNull(connection.getGeometries());
 		}catch(Exception e){
 			e.printStackTrace();
 			fail("This MUST have succeeded because the property file is valid! \nCaught exception: " + getPertinentMessage(e));
@@ -413,6 +431,46 @@ public class TestDefaultServiceConnection {
 			assertEquals(e.getClass(), TAPException.class);
 			assertEquals(e.getMessage(), "Class path expected for the property \"" + KEY_USER_IDENTIFIER + "\", instead of: \"foo\"!");
 		}
+
+		// Valid geometry list:
+		try{
+			ServiceConnection connection = new DefaultServiceConnection(geometriesProp);
+			assertNotNull(connection.getGeometries());
+			assertEquals(4, connection.getGeometries().size());
+			assertEquals("POINT", ((ArrayList<String>)connection.getGeometries()).get(0));
+			assertEquals("CIRCLE", ((ArrayList<String>)connection.getGeometries()).get(1));
+			assertEquals("CONTAINS", ((ArrayList<String>)connection.getGeometries()).get(2));
+			assertEquals("INTERSECTS", ((ArrayList<String>)connection.getGeometries()).get(3));
+		}catch(Exception e){
+			fail("This MUST have succeeded because the given list of geometries is correct! \nCaught exception: " + getPertinentMessage(e));
+		}
+
+		// "NONE" as geometry list:
+		try{
+			ServiceConnection connection = new DefaultServiceConnection(noneGeomProp);
+			assertNotNull(connection.getGeometries());
+			assertEquals(0, connection.getGeometries().size());
+		}catch(Exception e){
+			fail("This MUST have succeeded because the given list of geometries is correct (reduced to only NONE)! \nCaught exception: " + getPertinentMessage(e));
+		}
+
+		// "NONE" inside a geometry list:
+		try{
+			new DefaultServiceConnection(noneInsideGeomProp);
+			fail("This MUST have failed because the given geometry list contains at least 2 items, whose one is NONE!");
+		}catch(Exception e){
+			assertEquals(e.getClass(), TAPException.class);
+			assertEquals(e.getMessage(), "The special value \"" + VALUE_NONE + "\" can not be used inside a list! It MUST be used in replacement of a whole list to specify that no value is allowed.");
+		}
+
+		// Unknown geometrical function:
+		try{
+			new DefaultServiceConnection(unknownGeomProp);
+			fail("This MUST have failed because the given geometry list contains at least 1 unknown ADQL geometrical function!");
+		}catch(Exception e){
+			assertEquals(e.getClass(), TAPException.class);
+			assertEquals(e.getMessage(), "Unknown ADQL geometrical function: \"foo\"!");
+		}
 	}
 
 	public static final String getPertinentMessage(final Exception ex){
-- 
GitLab