Commit 5c07880a authored by gmantele's avatar gmantele
Browse files

[TAP] Add property to add/replace TAP resources & Add the tool function...

[TAP] Add property to add/replace TAP resources & Add the tool function TAPConfiguration.newInstance(...).
parent 0aeb2355
Loading
Loading
Loading
Loading
+8 −26
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import static tap.config.TAPConfiguration.VALUE_XML;
import static tap.config.TAPConfiguration.fetchClass;
import static tap.config.TAPConfiguration.getProperty;
import static tap.config.TAPConfiguration.isClassPath;
import static tap.config.TAPConfiguration.newInstance;
import static tap.config.TAPConfiguration.parseLimit;

import java.io.File;
@@ -123,6 +124,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
	private Collection<FunctionDef> udfs = new ArrayList<FunctionDef>(0);

	public ConfigurableServiceConnection(final Properties tapConfig) throws NullPointerException, TAPException, UWSException{
		if (tapConfig == null)
			throw new NullPointerException("Missing TAP properties! ");

		// 1. INITIALIZE THE FILE MANAGER:
		initFileManager(tapConfig);

@@ -198,20 +202,8 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
			}
		}
		// CUSTOM file manager:
		else{
			Class<? extends UWSFileManager> classObj = fetchClass(fileManagerType, KEY_FILE_MANAGER, UWSFileManager.class);
			if (classObj == null)
				throw new TAPException("Unknown value for the property \"" + KEY_FILE_MANAGER + "\": \"" + fileManagerType + "\". Only two possible values: " + VALUE_LOCAL + " or a class path between {...}.");

			try{
				fileManager = classObj.getConstructor(Properties.class).newInstance(tapConfig);
			}catch(Exception e){
				if (e instanceof TAPException)
					throw (TAPException)e;
		else
					throw new TAPException("Impossible to create a TAPFileManager instance with the constructor (java.util.Properties tapConfig) of \"" + classObj.getName() + "\" for the following reason: " + e.getMessage());
			}
		}
			fileManager = newInstance(fileManagerType, KEY_FILE_MANAGER, UWSFileManager.class, new Class<?>[]{Properties.class}, new Object[]{tapConfig});
	}

	private void initLogger(final Properties tapConfig){
@@ -458,18 +450,8 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
					hasVotableFormat = true;
			}
			// custom OutputFormat
			else if (isClassPath(f)){
				Class<? extends OutputFormat> userOutputFormatClass = fetchClass(f, KEY_OUTPUT_FORMATS, OutputFormat.class);
				try{
					OutputFormat userOutputFormat = userOutputFormatClass.getConstructor(ServiceConnection.class).newInstance(this);
					outputFormats.add(userOutputFormat);
				}catch(Exception e){
					if (e instanceof TAPException)
						throw (TAPException)e;
					else
						throw new TAPException("Impossible to create an OutputFormat instance with the constructor (ServiceConnection) of \"" + userOutputFormatClass.getName() + "\" (see the property output_add_format) for the following reason: " + e.getMessage());
				}
			}
			else if (isClassPath(f))
				outputFormats.add(TAPConfiguration.newInstance(f, KEY_OUTPUT_FORMATS, OutputFormat.class, new Class<?>[]{ServiceConnection.class}, new Object[]{this}));
			// unknown format
			else
				throw new TAPException("Unknown output format: " + f);
+3 −0
Original line number Diff line number Diff line
@@ -60,6 +60,9 @@ public final class ConfigurableTAPFactory extends AbstractTAPFactory {
	public ConfigurableTAPFactory(ServiceConnection service, final Properties tapConfig) throws NullPointerException, TAPException{
		super(service);

		if (tapConfig == null)
			throw new NullPointerException("Missing TAP properties! ");

		/* 1. Configure the database access */
		final String dbAccessMethod = getProperty(tapConfig, KEY_DATABASE_ACCESS);

+31 −18
Original line number Diff line number Diff line
@@ -20,18 +20,18 @@ package tap.config;
 */

import static tap.config.TAPConfiguration.DEFAULT_TAP_CONF_FILE;
import static tap.config.TAPConfiguration.KEY_ADD_TAP_RESOURCES;
import static tap.config.TAPConfiguration.KEY_HOME_PAGE;
import static tap.config.TAPConfiguration.KEY_HOME_PAGE_MIME_TYPE;
import static tap.config.TAPConfiguration.TAP_CONF_PARAMETER;
import static tap.config.TAPConfiguration.fetchClass;
import static tap.config.TAPConfiguration.getProperty;
import static tap.config.TAPConfiguration.isClassPath;
import static tap.config.TAPConfiguration.newInstance;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.Properties;

import javax.servlet.ServletConfig;
@@ -44,6 +44,7 @@ import tap.ServiceConnection;
import tap.TAPException;
import tap.resource.HomePage;
import tap.resource.TAP;
import tap.resource.TAPResource;

public class ConfigurableTAPServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
@@ -117,21 +118,10 @@ public class ConfigurableTAPServlet extends HttpServlet {
		if (propValue != null){
			// If it is a class path, replace the current home page by an instance of this class:
			if (isClassPath(propValue)){
				Class<? extends HomePage> newHomePage = null;
				try{
					// ...fetch the class:
					newHomePage = fetchClass(propValue, KEY_HOME_PAGE, HomePage.class);
					// ...get its constructor with TAP object:
					Constructor<? extends HomePage> constructor = newHomePage.getConstructor(TAP.class);
					// ...create a new instance and set it as new home page:
					tap.setHomePage(constructor.newInstance(tap));
				}catch(NoSuchMethodException e){
					throw new ServletException("Missing constructor " + (newHomePage == null ? "HomePage" : newHomePage.getName()) + "(TAP)! This constructor is required to set a new home page to your TAP service.");
				}catch(Exception ex){
					if (ex instanceof TAPException)
						throw new ServletException(ex.getMessage(), (ex.getCause() == null ? ex : ex.getCause()));
					else
						throw new ServletException("Impossible to set the specified home page: \"" + propValue + "\"!", ex);
					tap.setHomePage(newInstance(propValue, KEY_HOME_PAGE, HomePage.class, new Class<?>[]{TAP.class}, new Object[]{tap}));
				}catch(TAPException te){
					throw new ServletException(te.getMessage(), te.getCause());
				}
			}
			// If it is a file URI (null, file inside WebContent, file://..., http://..., etc...):
@@ -145,10 +135,33 @@ public class ConfigurableTAPServlet extends HttpServlet {
			}
		}

		/* 5. DEFAULT SERVLET INITIALIZATION */
		/* 5. SET ADDITIONAL TAP RESOURCES */
		propValue = getProperty(tapConf, KEY_ADD_TAP_RESOURCES);
		if (propValue != null){
			// split all list items:
			String[] lstResources = propValue.split(",");
			for(String addRes : lstResources){
				addRes = addRes.trim();
				// ignore empty items:
				if (addRes.length() > 0){
					try{
						// create an instance of the resource:
						TAPResource newRes = newInstance(addRes, KEY_ADD_TAP_RESOURCES, TAPResource.class, new Class<?>[]{TAP.class}, new Object[]{tap});
						if (newRes.getName() == null || newRes.getName().trim().length() == 0)
							throw new TAPException("TAP resource name missing for the new resource \"" + addRes + "\"! The function getName() of the new TAPResource must return a non-empty and not NULL name. See the property \"" + KEY_ADD_TAP_RESOURCES + "\".");
						// add it into TAP:
						tap.addResource(newRes);
					}catch(TAPException te){
						throw new ServletException(te.getMessage(), te.getCause());
					}
				}
			}
		}

		/* 6. DEFAULT SERVLET INITIALIZATION */
		super.init(config);

		/* 6. FINALLY MAKE THE SERVICE AVAILABLE */
		/* 7. FINALLY MAKE THE SERVICE AVAILABLE */
		serviceConn.setAvailable(true, "TAP service available.");
	}

+98 −0
Original line number Diff line number Diff line
@@ -2,6 +2,8 @@ package tap.config;

import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Properties;
@@ -113,6 +115,9 @@ public final class TAPConfiguration {
	public final static String KEY_UDFS = "udfs";
	public final static String VALUE_ANY = "ANY";

	/* ADDITIONAL TAP RESOURCES */
	public final static String KEY_ADD_TAP_RESOURCES = "additional_resources";

	/**
	 * <p>Read the asked property from the given Properties object.</p>
	 * <ul>
@@ -187,6 +192,99 @@ public final class TAPConfiguration {
		}
	}

	/**
	 * <p>Create an instance of the specified class. The class name is expected to be surrounded by {} in the given value.</p>
	 * 
	 * <p>The instance is created using the empty constructor of the specified class.</p>
	 * 
	 * @param value			Value which is supposed to contain the classpath between brackets (see {@link #isClassPath(String)} for more details)
	 * @param propertyName	Name of the property associated with the parameter "value".
	 * @param expectedType	Type of the class expected to be returned ; it is also the type which parameterizes this function: C.
	 * 
	 * @return	The corresponding instance.
	 * 
	 * @throws TAPException	If the class name is incorrect
	 *                     	or if its type is not compatible with the parameterized type C (represented by the parameter "expectedType")
	 *                     	or if the specified class has no empty constructor
	 *                     	or if an error occurred while calling this constructor.
	 * 
	 * @see {@link #isClassPath(String)}
	 * @see #fetchClass(String, String, Class)
	 */
	public final static < C > C newInstance(final String propValue, final String propName, final Class<C> expectedType) throws TAPException{
		return newInstance(propValue, propName, expectedType, null, null);
	}

	/**
	 * <p>Create an instance of the specified class. The class name is expected to be surrounded by {} in the given value.</p>
	 * 
	 * <p><b>IMPORTANT:</b>
	 * 	The instance is created using the constructor whose the declaration matches exactly with the given list of parameter types.
	 * 	The number and types of given parameters MUST match exactly to the list of parameter types.
	 * </p>
	 * 
	 * @param value			Value which is supposed to contain the classpath between brackets (see {@link #isClassPath(String)} for more details)
	 * @param propertyName	Name of the property associated with the parameter "value".
	 * @param expectedType	Type of the class expected to be returned ; it is also the type which parameterizes this function: C.
	 * @param pTypes		List of each constructor parameter type. Each type MUST be exactly the type declared in the class constructor to select. <i>NULL or empty array if no parameter.</i>
	 * @param parameters	List of all constructor parameters. The number of object MUST match exactly the number of classes provided in the parameter pTypes. <i>NULL or empty array if no parameter.</i>
	 * 
	 * @return	The corresponding instance.
	 * 
	 * @throws TAPException	If the class name is incorrect
	 *                     	or if its type is not compatible with the parameterized type C (represented by the parameter "expectedType")
	 *                     	or if the constructor with the specified parameters can not be found
	 *                     	or if an error occurred while calling this constructor.
	 * 
	 * @see {@link #isClassPath(String)}
	 * @see #fetchClass(String, String, Class)
	 */
	public final static < C > C newInstance(final String propValue, final String propName, final Class<C> expectedType, final Class<?>[] pTypes, final Object[] parameters) throws TAPException{
		// Ensure the given name is a class name specification:
		if (!isClassPath(propValue))
			throw new TAPException("Class name expected for the property \"" + propName + "\" instead of: \"" + propValue + "\"! The specified class must extend/implement " + expectedType.getName() + ".");

		Class<? extends C> classObj = null;
		try{

			// Fetch the class object:
			classObj = fetchClass(propValue, propName, expectedType);

			// Get a constructor matching the given parameters list:
			Constructor<? extends C> constructor = classObj.getConstructor((pTypes == null) ? new Class<?>[0] : pTypes);

			// Finally create a new instance:
			return constructor.newInstance((parameters == null) ? new Object[0] : parameters);

		}catch(NoSuchMethodException e){
			// List parameters' type:
			StringBuffer pTypesStr = new StringBuffer();
			for(int i = 0; i < pTypes.length; i++){
				if (pTypesStr.length() > 0)
					pTypesStr.append(", ");
				if (pTypes[i] == null)
					pTypesStr.append("NULL");
				pTypesStr.append(pTypes[i].getName());
			}
			// Throw the error:
			throw new TAPException("Missing constructor " + classObj.getName() + "(" + pTypesStr.toString() + ")! See the value \"" + propValue + "\" of the property \"" + propName + "\".");
		}catch(InstantiationException ie){
			throw new TAPException("Impossible to create an instance of an abstract class: \"" + classObj.getName() + "\"! See the value \"" + propValue + "\" of the property \"" + propName + "\".");
		}catch(InvocationTargetException ite){
			if (ite.getCause() != null){
				if (ite.getCause() instanceof TAPException)
					throw (TAPException)ite.getCause();
				else
					throw new TAPException(ite.getCause());
			}else
				throw new TAPException(ite);
		}catch(TAPException te){
			throw te;
		}catch(Exception ex){
			throw new TAPException("Impossible to create an instance of " + expectedType.getName() + " as specified in the property \"" + propName + "\": \"" + propValue + "\"!", ex);
		}
	}

	/**
	 * <p>Lets parsing a limit (for output, upload, ...) with its numeric value and its unit.</p>
	 * <p>
+25 −0
Original line number Diff line number Diff line
@@ -595,6 +595,31 @@
				<td><ul><li>ø <em>(default)</em></li><li>ANY</li><li>[trim(txt String) -&gt; String], [random() -&gt; DOUBLE]</li><li>[newFct(x double)-&gt;double, {apackage.MyNewFunction}]</li></ul></td>
			</tr>
			
			<tr><td colspan="5">Additional TAP Resources</td></tr>
			<tr class="optional">
				<td class="done">additional_resources</td>
				<td></td>
				<td>text</td>
				<td>
					<p>Comma-separated list of additional TAP resources/end-point.</p>
					<p>
						By default, the following standard TAP resources are already existing: /sync, /async, /tables, /capabilities and /availability.
						With this property, you can add a custom resource to your TAP service (e.g. /adqlValidator, /admin).
					</p>
					<p>
						Each item of the list MUST be the name of a class implementing tap.resource.TAPResource. This class MUST have at least one constructor
						with exactly one parameter of type tap.resource.TAP.
					</p>
					<p>
						The string returned by tap.resource.TAPResource.getName() will be the resource name, following the root TAP service URL (e.g. if getName()
						returns "foo", then its access URL will "{tapRoot}/foo"). Then, it is possible to replace TAP resources already existing by using the same
						name (e.g. if getName() returns "sync", the /sync resource won't be anymore the default Sync resource of this library but your new resource).
					</p>
					<p><em>By default, this list is empty ; only the standard TAP resources exist.</em></p>
				</td>
				<td>{aPackage.QuickADQLValidator}</td>
			</tr>
			
		</table>
		<script type="text/javascript">
			var nb = document.getElementsByClassName("mandatory").length;
Loading