Skip to content
DefaultServiceConnection.java 26.2 KiB
Newer Older
import static tap.config.TAPConfiguration.DEFAULT_DIRECTORY_PER_USER;
import static tap.config.TAPConfiguration.DEFAULT_EXECUTION_DURATION;
import static tap.config.TAPConfiguration.DEFAULT_GROUP_USER_DIRECTORIES;
import static tap.config.TAPConfiguration.DEFAULT_MAX_ASYNC_JOBS;
import static tap.config.TAPConfiguration.DEFAULT_RETENTION_PERIOD;
import static tap.config.TAPConfiguration.DEFAULT_UPLOAD_MAX_FILE_SIZE;
import static tap.config.TAPConfiguration.KEY_DEFAULT_EXECUTION_DURATION;
import static tap.config.TAPConfiguration.KEY_DEFAULT_OUTPUT_LIMIT;
import static tap.config.TAPConfiguration.KEY_DEFAULT_RETENTION_PERIOD;
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;
import static tap.config.TAPConfiguration.KEY_MAX_OUTPUT_LIMIT;
import static tap.config.TAPConfiguration.KEY_MAX_RETENTION_PERIOD;
import static tap.config.TAPConfiguration.KEY_MAX_UPLOAD_LIMIT;
import static tap.config.TAPConfiguration.KEY_METADATA;
import static tap.config.TAPConfiguration.KEY_METADATA_FILE;
import static tap.config.TAPConfiguration.KEY_OUTPUT_FORMATS;
import static tap.config.TAPConfiguration.KEY_PROVIDER_NAME;
import static tap.config.TAPConfiguration.KEY_SERVICE_DESCRIPTION;
import static tap.config.TAPConfiguration.KEY_UPLOAD_ENABLED;
import static tap.config.TAPConfiguration.KEY_UPLOAD_MAX_FILE_SIZE;
import static tap.config.TAPConfiguration.KEY_USER_IDENTIFIER;
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 static tap.config.TAPConfiguration.fetchClass;
import static tap.config.TAPConfiguration.getProperty;
import static tap.config.TAPConfiguration.isClassPath;
import static tap.config.TAPConfiguration.parseLimit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Properties;

import tap.ServiceConnection;
import tap.TAPException;
import tap.TAPFactory;
import tap.formatter.OutputFormat;
import tap.formatter.SVFormat;
import tap.formatter.VOTableFormat;
import tap.log.DefaultTAPLog;
import tap.log.TAPLog;
import tap.metadata.TAPMetadata;
import tap.metadata.TableSetParser;
import uws.UWSException;
import uws.service.UserIdentifier;
import uws.service.file.LocalUWSFileManager;
import uws.service.file.UWSFileManager;
import adql.db.FunctionDef;
public final class DefaultServiceConnection implements ServiceConnection {

	private TAPLog logger;

	private DefaultTAPFactory tapFactory;

	private final String providerName;
	private final String serviceDescription;

	private boolean isAvailable = false;	// the TAP service must be disabled until the end of its connection initialization 
	private String availability = "TAP service not yet initialized.";
	private int maxAsyncJobs = DEFAULT_MAX_ASYNC_JOBS;

	private int[] executionDuration = new int[2];
	private int[] retentionPeriod = new int[2];

	private final ArrayList<OutputFormat> outputFormats;
	private int[] outputLimits = new int[]{-1,-1};
	private LimitUnit[] outputLimitTypes = new LimitUnit[2];

	private boolean isUploadEnabled = false;
	private int[] uploadLimits = new int[]{-1,-1};
	private LimitUnit[] uploadLimitTypes = new LimitUnit[2];
	private int maxUploadSize = DEFAULT_UPLOAD_MAX_FILE_SIZE;

	private UserIdentifier userIdentifier = null;

	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:
		initFileManager(tapConfig);

		// 2. CREATE THE LOGGER:
		logger = new DefaultTAPLog(fileManager);

		// 3. BUILD THE TAP FACTORY:
		tapFactory = new DefaultTAPFactory(this, tapConfig);

		// 4. GET THE METADATA:
		metadata = initMetadata(tapConfig);

		// 5. SET ALL GENERAL SERVICE CONNECTION INFORMATION:
		providerName = getProperty(tapConfig, KEY_PROVIDER_NAME);
		serviceDescription = getProperty(tapConfig, KEY_SERVICE_DESCRIPTION);
		initRetentionPeriod(tapConfig);
		initExecutionDuration(tapConfig);

		// default output format = VOTable:
		outputFormats = new ArrayList<OutputFormat>(1);
		outputFormats.add(new VOTableFormat(this));
		// set additional output formats:
		addOutputFormats(tapConfig);
		// set output limits:
		initOutputLimits(tapConfig);

		// is upload enabled ?
		isUploadEnabled = Boolean.parseBoolean(getProperty(tapConfig, KEY_UPLOAD_ENABLED));
		// set upload limits:
		initUploadLimits(tapConfig);
		// set the maximum upload file size:
		initMaxUploadSize(tapConfig);

		// 8. SET A USER IDENTIFIER:
		initUserIdentifier(tapConfig);

		// 9. CONFIGURE ADQL:
		initADQLGeometries(tapConfig);

		// 10. MAKE THE SERVICE AVAILABLE:
		setAvailable(true, "TAP service available.");
	}

	private void initFileManager(final Properties tapConfig) throws TAPException{
		// Read the desired file manager:
		String fileManagerType = getProperty(tapConfig, KEY_FILE_MANAGER);
		if (fileManagerType == null)
			throw new TAPException("The property \"" + KEY_FILE_MANAGER + "\" is missing! It is required to create a TAP Service. Two possible values: " + VALUE_LOCAL + " or a class path between {...}.");
		else
			fileManagerType = fileManagerType.trim();

		// LOCAL file manager:
		if (fileManagerType.equalsIgnoreCase(VALUE_LOCAL)){
			// Read the desired root path:
			String rootPath = getProperty(tapConfig, KEY_FILE_ROOT_PATH);
			if (rootPath == null)
				throw new TAPException("The property \"" + KEY_FILE_ROOT_PATH + "\" is missing! It is required to create a TAP Service. Please provide a path toward a directory which will contain all files related to the service.");
			File rootFile = new File(rootPath);

			// Determine whether there should be one directory for each user:
			String propValue = getProperty(tapConfig, KEY_DIRECTORY_PER_USER);
			boolean oneDirectoryPerUser = (propValue == null) ? DEFAULT_DIRECTORY_PER_USER : Boolean.parseBoolean(propValue);

			// Determine whether there should be one directory for each user:
			propValue = getProperty(tapConfig, KEY_GROUP_USER_DIRECTORIES);
			boolean groupUserDirectories = (propValue == null) ? DEFAULT_GROUP_USER_DIRECTORIES : Boolean.parseBoolean(propValue);

			// Build the Local TAP File Manager:
			try{
				fileManager = new LocalUWSFileManager(rootFile, oneDirectoryPerUser, groupUserDirectories);
			}catch(UWSException e){
				throw new TAPException("The property \"" + KEY_FILE_ROOT_PATH + "\" (" + rootPath + ") is incorrect: " + e.getMessage());
			}
		}
		// CUSTOM file manager:
		else{
			Class<? extends UWSFileManager> classObj = fetchClass(fileManagerType, KEY_FILE_MANAGER, UWSFileManager.class);
				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());
	private TAPMetadata initMetadata(final Properties tapConfig) throws TAPException{
		// Get the fetching method to use:
		String metaFetchType = getProperty(tapConfig, KEY_METADATA);
		if (metaFetchType == null)
			throw new TAPException("The property \"" + KEY_METADATA + "\" is missing! It is required to create a TAP Service. Two possible values: " + VALUE_XML + " (to get metadata from a TableSet XML document) or " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA).");

		TAPMetadata metadata = null;

		// GET METADATA FROM XML & UPDATE THE DATABASE (schema TAP_SCHEMA only):
		if (metaFetchType.equalsIgnoreCase(VALUE_XML)){
			// Get the XML file path:
			String xmlFilePath = getProperty(tapConfig, KEY_METADATA_FILE);
			if (xmlFilePath == null)
				throw new TAPException("The property \"" + KEY_METADATA_FILE + "\" is missing! According to the property \"" + KEY_METADATA + "\", metadata must be fetched from an XML document. The local file path of it MUST be provided using the property \"" + KEY_METADATA_FILE + "\".");

			// Parse the XML document and build the corresponding metadata:
			try{
				metadata = (new TableSetParser()).parse(new File(xmlFilePath));
			}catch(IOException ioe){
				throw new TAPException("A grave error occurred while reading/parsing the TableSet XML document: \"" + xmlFilePath + "\"!", ioe);
			}

			// Update the database:
			DBConnection conn = null;
			try{
				conn = tapFactory.getConnection("SET_TAP_SCHEMA");
				conn.setTAPSchema(metadata);
			}finally{
				if (conn != null)
					tapFactory.freeConnection(conn);
			}
		}
		// GET METADATA FROM DATABASE (schema TAP_SCHEMA):
		else if (metaFetchType.equalsIgnoreCase(VALUE_DB)){
			DBConnection conn = null;
			try{
				conn = tapFactory.getConnection("GET_TAP_SCHEMA");
				metadata = conn.getTAPSchema();
			}finally{
				if (conn != null)
					tapFactory.freeConnection(conn);
			}
		}
		// INCORRECT VALUE => ERROR!
		else
			throw new TAPException("Unsupported value for the property \"" + KEY_METADATA + "\": \"" + metaFetchType + "\"! Only two values are allowed: " + VALUE_XML + " (to get metadata from a TableSet XML document) or " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA).");

		return metadata;
	}

	private void initMaxAsyncJobs(final Properties tapConfig) throws TAPException{
		// Get the property value:
		String propValue = getProperty(tapConfig, KEY_MAX_ASYNC_JOBS);
		try{
			// If a value is provided, cast it into an integer and set the attribute:
			maxAsyncJobs = (propValue == null) ? DEFAULT_MAX_ASYNC_JOBS : Integer.parseInt(propValue);
		}catch(NumberFormatException nfe){
			throw new TAPException("Integer expected for the property \"" + KEY_MAX_ASYNC_JOBS + "\", instead of: \"" + propValue + "\"!");
	private void initRetentionPeriod(final Properties tapConfig) throws TAPException{
		retentionPeriod = new int[2];

		// Set the default period:
		String propValue = getProperty(tapConfig, KEY_DEFAULT_RETENTION_PERIOD);
		try{
			retentionPeriod[0] = (propValue == null) ? DEFAULT_RETENTION_PERIOD : Integer.parseInt(propValue);
		}catch(NumberFormatException nfe){
			throw new TAPException("Integer expected for the property \"" + KEY_DEFAULT_RETENTION_PERIOD + "\", instead of: \"" + propValue + "\"!");
		}

		// Set the maximum period:
		propValue = getProperty(tapConfig, KEY_MAX_RETENTION_PERIOD);
		try{
			retentionPeriod[1] = (propValue == null) ? DEFAULT_RETENTION_PERIOD : Integer.parseInt(propValue);
		}catch(NumberFormatException nfe){
			throw new TAPException("Integer expected for the property \"" + KEY_MAX_RETENTION_PERIOD + "\", instead of: \"" + propValue + "\"!");
		}

		// The maximum period MUST be greater or equals than the default period.
		// If not, the default period is set (so decreased) to the maximum period.
		if (retentionPeriod[1] > 0 && retentionPeriod[1] < retentionPeriod[0])
			retentionPeriod[0] = retentionPeriod[1];
	}

	private void initExecutionDuration(final Properties tapConfig) throws TAPException{
		executionDuration = new int[2];

		// Set the default duration:
		String propValue = getProperty(tapConfig, KEY_DEFAULT_EXECUTION_DURATION);
		try{
			executionDuration[0] = (propValue == null) ? DEFAULT_EXECUTION_DURATION : Integer.parseInt(propValue);
		}catch(NumberFormatException nfe){
			throw new TAPException("Integer expected for the property \"" + KEY_DEFAULT_EXECUTION_DURATION + "\", instead of: \"" + propValue + "\"!");
		}

		// Set the maximum duration:
		propValue = getProperty(tapConfig, KEY_MAX_EXECUTION_DURATION);
		try{
			executionDuration[1] = (propValue == null) ? DEFAULT_EXECUTION_DURATION : Integer.parseInt(propValue);
		}catch(NumberFormatException nfe){
			throw new TAPException("Integer expected for the property \"" + KEY_MAX_EXECUTION_DURATION + "\", instead of: \"" + propValue + "\"!");
		}

		// The maximum duration MUST be greater or equals than the default duration.
		// If not, the default duration is set (so decreased) to the maximum duration.
		if (executionDuration[1] > 0 && executionDuration[1] < executionDuration[0])
			executionDuration[0] = executionDuration[1];
	}

	private void addOutputFormats(final Properties tapConfig) throws TAPException{
		// Fetch the value of the property for additional output formats:
		String formats = TAPConfiguration.getProperty(tapConfig, KEY_OUTPUT_FORMATS);

		// Since it is a comma separated list of output formats, a loop will parse this list comma by comma:
		String f;
		int indexSep;
		while(formats != null && formats.length() > 0){
			// Get a format item from the list:
			indexSep = formats.indexOf(',');
			// no comma => only one format
			if (indexSep < 0){
				f = formats;
				formats = null;
			}
			// comma at the first position => empty list item => go to the next item
			else if (indexSep == 0){
				formats = formats.substring(1).trim();
				continue;
			}
			// else => get the first format item, and then remove it from the list for the next iteration
			else{
				f = formats.substring(0, indexSep).trim();
				formats = formats.substring(indexSep + 1).trim();
			}

			// Identify the format and append it to the output format list of the service:
			// JSON
			if (f.equalsIgnoreCase(VALUE_JSON))
			// CSV
			else if (f.equalsIgnoreCase(VALUE_CSV))
				outputFormats.add(new SVFormat(this, ",", true));
			// TSV
			else if (f.equalsIgnoreCase(VALUE_TSV))
				outputFormats.add(new SVFormat(this, "\t", true));
			// any SV (separated value) format
			else if (f.toLowerCase().startsWith(VALUE_SV)){
				// get the separator:
				int endSep = f.indexOf(')');
				if (VALUE_SV.length() < f.length() && f.charAt(VALUE_SV.length()) == '(' && endSep > VALUE_SV.length() + 1){
					String separator = f.substring(VALUE_SV.length() + 1, f.length() - 1);
					// get the MIME type and its alias, if any of them is provided:
					String mimeType = null, shortMimeType = null;
					if (endSep + 1 < f.length() && f.charAt(endSep + 1) == ':'){
						int endMime = f.indexOf(':', endSep + 2);
						if (endMime < 0)
							mimeType = f.substring(endSep + 2, f.length());
						else if (endMime > 0){
							mimeType = f.substring(endSep + 2, endMime);
							shortMimeType = f.substring(endMime + 1);
						}
					}
					// add the defined SV(...) format:
					outputFormats.add(new SVFormat(this, separator, true, mimeType, shortMimeType));
				}else
					throw new TAPException("Missing separator char/string for the SV output format: \"" + f + "\"!");
			}
			// 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());
				}
			}
			// unknown format
			else
				throw new TAPException("Unknown output format: " + f);
		}
	}

	private void initOutputLimits(final Properties tapConfig) throws TAPException{
		Object[] limit = parseLimit(getProperty(tapConfig, KEY_DEFAULT_OUTPUT_LIMIT), KEY_DEFAULT_OUTPUT_LIMIT, false);
		outputLimitTypes[0] = (LimitUnit)limit[1];	// it should be "rows" since the parameter areBytesAllowed of parseLimit =false

		limit = parseLimit(getProperty(tapConfig, KEY_MAX_OUTPUT_LIMIT), KEY_DEFAULT_OUTPUT_LIMIT, false);
		outputLimitTypes[1] = (LimitUnit)limit[1];	// it should be "rows" since the parameter areBytesAllowed of parseLimit =false
			throw new TAPException("The default output limit (here: " + outputLimits[0] + ") MUST be less or equal to the maximum output limit (here: " + limit[0] + ")!");
	}

	private void initUploadLimits(final Properties tapConfig) throws TAPException{
		Object[] limit = parseLimit(getProperty(tapConfig, KEY_DEFAULT_UPLOAD_LIMIT), KEY_DEFAULT_UPLOAD_LIMIT, true);
		uploadLimitTypes[0] = (LimitUnit)limit[1];

		limit = parseLimit(getProperty(tapConfig, KEY_MAX_UPLOAD_LIMIT), KEY_MAX_UPLOAD_LIMIT, true);
		if (!((LimitUnit)limit[1]).isCompatibleWith(uploadLimitTypes[0]))
			throw new TAPException("The default upload limit (in " + uploadLimitTypes[0] + ") and the maximum upload limit (in " + limit[1] + ") MUST be expressed in the same unit!");
		else
			uploadLimitTypes[1] = (LimitUnit)limit[1];

			throw new TAPException("The default upload limit (here: " + getProperty(tapConfig, KEY_DEFAULT_UPLOAD_LIMIT) + ") MUST be less or equal to the maximum upload limit (here: " + getProperty(tapConfig, KEY_MAX_UPLOAD_LIMIT) + ")!");
	}

	private void initMaxUploadSize(final Properties tapConfig) throws TAPException{
		String propValue = getProperty(tapConfig, KEY_UPLOAD_MAX_FILE_SIZE);
		// If a value is specified...
		if (propValue != null){
			// ...parse the value:
			Object[] limit = parseLimit(propValue, KEY_UPLOAD_MAX_FILE_SIZE, true);
			// ...check that the unit is correct (bytes): 
			if (!LimitUnit.bytes.isCompatibleWith((LimitUnit)limit[1]))
				throw new TAPException("The maximum upload file size " + KEY_UPLOAD_MAX_FILE_SIZE + " (here: " + propValue + ") can not be expressed in a unit different from bytes (B, kB, MB, GB)!");
			// ...set the max file size:
			int value = (int)((Integer)limit[0] * ((LimitUnit)limit[1]).bytesFactor());
	private void initUserIdentifier(final Properties tapConfig) throws TAPException{
		// Get the property value:
		String propValue = getProperty(tapConfig, KEY_USER_IDENTIFIER);
		if (propValue == null)
			return;

		// Check the value is a class path:
		if (!isClassPath(propValue))
			throw new TAPException("Class path expected for the property \"" + KEY_USER_IDENTIFIER + "\", instead of: \"" + propValue + "\"!");

		// Fetch the class:
		Class<? extends UserIdentifier> c = fetchClass(propValue, KEY_USER_IDENTIFIER, UserIdentifier.class);

		// Create an instance with the empty constructor:
		try{
			userIdentifier = c.getConstructor().newInstance();
		}catch(Exception e){
			if (e instanceof TAPException)
				throw (TAPException)e;
			else
				throw new TAPException("Impossible to create a UserIdentifier instance with the empty constructor of \"" + c.getName() + "\" (see the property user_identifier) for the following reason: " + e.getMessage());
		}
	}

	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;
	}

	@Override
	public String getProviderDescription(){
		return serviceDescription;
	}

	@Override
	public boolean isAvailable(){
		return isAvailable;
	}

	@Override
	public String getAvailability(){
		return availability;
	}

	@Override
	public void setAvailable(boolean isAvailable, String message){
		this.isAvailable = isAvailable;
		availability = message;
	}

	@Override
	public int[] getRetentionPeriod(){
		return retentionPeriod;
	}

	public boolean setDefaultRetentionPeriod(final int period){
		if ((retentionPeriod[1] <= 0) || (period > 0 && period <= retentionPeriod[1])){
			retentionPeriod[0] = period;
			return true;
		}else
			return false;
	}

	public boolean setMaxRetentionPeriod(final int period){
		if (period <= 0 || (retentionPeriod[0] > 0 && period >= retentionPeriod[0])){
			retentionPeriod[1] = period;
			return true;
		}else
			return false;
	}

	@Override
	public int[] getExecutionDuration(){
		return executionDuration;
	}

	public boolean setDefaultExecutionDuration(final int period){
		if ((executionDuration[1] <= 0) || (period > 0 && period <= executionDuration[1])){
			executionDuration[0] = period;
			return true;
		}else
			return false;
	}

	public boolean setMaxExecutionDuration(final int period){
		if (period <= 0 || (executionDuration[0] > 0 && period >= executionDuration[0])){
			executionDuration[1] = period;
			return true;
		}else
			return false;
	}

	@Override
	public Iterator<OutputFormat> getOutputFormats(){
		return outputFormats.iterator();
	public OutputFormat getOutputFormat(final String mimeOrAlias){
		if (mimeOrAlias == null || mimeOrAlias.trim().isEmpty())
			return null;

			if ((f.getMimeType() != null && f.getMimeType().equalsIgnoreCase(mimeOrAlias)) || (f.getShortMimeType() != null && f.getShortMimeType().equalsIgnoreCase(mimeOrAlias)))
				return f;
		}
	public void addOutputFormat(final OutputFormat newOutputFormat){
		outputFormats.add(newOutputFormat);
	public boolean removeOutputFormat(final String mimeOrAlias){
		OutputFormat of = getOutputFormat(mimeOrAlias);
		if (of != null)
			return outputFormats.remove(of);
		else
			return false;
	public int[] getOutputLimit(){
		return outputLimits;
	public boolean setDefaultOutputLimit(final int limit){
		if ((outputLimits[1] <= 0) || (limit > 0 && limit <= outputLimits[1])){
			outputLimits[0] = limit;
			return true;
		}else
			return false;
	public boolean setMaxOutputLimit(final int limit){
		if (limit > 0 && outputLimits[0] > 0 && limit < outputLimits[0])
			return false;
		else{
			outputLimits[1] = limit;
			return true;
		}
	public final LimitUnit[] getOutputLimitType(){
		return new LimitUnit[]{LimitUnit.rows,LimitUnit.rows};
	}

	@Override
	public Collection<String> getCoordinateSystems(){
		return null;
	}

	@Override
	public TAPLog getLogger(){
		return logger;
	}

	@Override
	public boolean uploadEnabled(){
		return isUploadEnabled;
	public void setUploadEnabled(final boolean enabled){
		isUploadEnabled = enabled;
	}

	@Override
	public int[] getUploadLimit(){
	}

	@Override
	public LimitUnit[] getUploadLimitType(){
		return uploadLimitTypes;
	}

	public void setUploadLimitType(final LimitUnit type){
		if (type != null)
			uploadLimitTypes = new LimitUnit[]{type,type};
	}

	public boolean setDefaultUploadLimit(final int limit){
		try{
			if ((uploadLimits[1] <= 0) || (limit > 0 && LimitUnit.compare(limit, uploadLimitTypes[0], uploadLimits[1], uploadLimitTypes[1]) <= 0)){
				uploadLimits[0] = limit;
				return true;
			}
		}catch(TAPException e){}
		return false;
	}

	public boolean setMaxUploadLimit(final int limit){
		try{
			if (limit > 0 && uploadLimits[0] > 0 && LimitUnit.compare(limit, uploadLimitTypes[1], uploadLimits[0], uploadLimitTypes[0]) < 0)
				return false;
			else{
				uploadLimits[1] = limit;
				return true;
			}
		}catch(TAPException e){
			return false;
		}
	public int getMaxUploadSize(){
		return maxUploadSize;
	}

	public boolean setMaxUploadSize(final int maxSize){
		// No "unlimited" value possible there:
		if (maxSize <= 0)
			return false;

		// Otherwise, set the maximum upload file size:
		maxUploadSize = maxSize;
		return true;
	}

	@Override
	public int getNbMaxAsyncJobs(){
		return maxAsyncJobs;
	}

	@Override
	public UserIdentifier getUserIdentifier(){
	}

	@Override
	public TAPMetadata getTAPMetadata(){
	@Override
	public Collection<String> getGeometries(){
	}

	@Override
	public Collection<FunctionDef> getUDFs(){
		return udfs;	// FORBID ANY UNKNOWN FUNCTION
	}