Skip to content
DefaultServiceConnection.java 17.3 KiB
Newer Older
/*
 * This file is part of TAPLibrary.
 * 
 * TAPLibrary is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * TAPLibrary 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Copyright 2013 - Astronomisches Rechen Institute (ARI)
 */

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_IS_AVAILABLE;
import static tap.config.TAPConfiguration.DEFAULT_RETENTION_PERIOD;
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_DIRECTORY_PER_USER;
import static tap.config.TAPConfiguration.KEY_DISABILITY_REASON;
import static tap.config.TAPConfiguration.KEY_FILE_MANAGER;
import static tap.config.TAPConfiguration.KEY_FILE_ROOT_PATH;
import static tap.config.TAPConfiguration.KEY_GROUP_USER_DIRECTORIES;
import static tap.config.TAPConfiguration.KEY_IS_AVAILABLE;
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_OUTPUT_FORMATS;
import static tap.config.TAPConfiguration.KEY_PROVIDER_NAME;
import static tap.config.TAPConfiguration.KEY_SERVICE_DESCRIPTION;
import static tap.config.TAPConfiguration.VALUE_CSV;
import static tap.config.TAPConfiguration.VALUE_JSON;
import static tap.config.TAPConfiguration.VALUE_LOCAL;
import static tap.config.TAPConfiguration.VALUE_SV;
import static tap.config.TAPConfiguration.VALUE_TSV;
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.io.File;
import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
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.file.LocalTAPFileManager;
import tap.file.TAPFileManager;
import tap.formatter.OutputFormat;
import tap.formatter.ResultSet2JsonFormatter;
import tap.formatter.ResultSet2SVFormatter;
import tap.formatter.ResultSet2VotableFormatter;
import tap.log.DefaultTAPLog;
import tap.log.TAPLog;
import tap.metadata.TAPMetadata;
import uws.UWSException;
import uws.service.UserIdentifier;

/**
 * 
 * @author Gr&eacute;gory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de
 * @version 1.1 (12/2013)
 */
public final class DefaultServiceConnection implements ServiceConnection<ResultSet> {

	private TAPFileManager fileManager;

	private TAPLog logger;

	private DefaultTAPFactory tapFactory;

	private final String providerName;
	private final String serviceDescription;

	private boolean isAvailable = false;
	private String availability = null;

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

	private final ArrayList<OutputFormat<ResultSet>> outputFormats;

	private int[] outputLimits = new int[2];
	private LimitUnit[] outputLimitTypes = new LimitUnit[2];

	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. SET ALL GENERAL SERVICE CONNECTION INFORMATION:
		providerName = getProperty(tapConfig, KEY_PROVIDER_NAME);
		serviceDescription = getProperty(tapConfig, KEY_SERVICE_DESCRIPTION);
		availability = getProperty(tapConfig, KEY_DISABILITY_REASON);
		initRetentionPeriod(tapConfig);
		initExecutionDuration(tapConfig);

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

		// 5. MAKE THE SERVICE AVAILABLE (or not, depending on the property value):
		String propValue = getProperty(tapConfig, KEY_IS_AVAILABLE);
		isAvailable = (propValue == null) ? DEFAULT_IS_AVAILABLE : Boolean.parseBoolean(propValue);
	}

	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 LocalTAPFileManager(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 TAPFileManager> classObj = fetchClass(fileManagerType, KEY_FILE_MANAGER, TAPFileManager.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(InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e){
				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 void initRetentionPeriod(final Properties tapConfig){
		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){
			retentionPeriod[0] = DEFAULT_RETENTION_PERIOD;
		}

		// 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){
			retentionPeriod[1] = DEFAULT_RETENTION_PERIOD;
		}

		// 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){
		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){
			executionDuration[0] = DEFAULT_EXECUTION_DURATION;
		}

		// 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){
			executionDuration[1] = DEFAULT_EXECUTION_DURATION;
		}

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

	@SuppressWarnings({"unchecked","rawtypes"})
	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))
				outputFormats.add(new ResultSet2JsonFormatter(this));
			// CSV
			else if (f.equalsIgnoreCase(VALUE_CSV))
				outputFormats.add(new ResultSet2SVFormatter(this, ",", true));
			// TSV
			else if (f.equalsIgnoreCase(VALUE_TSV))
				outputFormats.add(new ResultSet2SVFormatter(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 ResultSet2SVFormatter(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<ResultSet> userOutputFormat = userOutputFormatClass.getConstructor(ServiceConnection.class).newInstance(this);
					outputFormats.add(userOutputFormat);
				}catch(InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e){
					throw new TAPException("Impossible to create an OutputFormat<ResultSet> instance with the constructor (ServiceConnection<ResultSet>) 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];
		setDefaultOutputLimit((int)limit[0]);

		limit = parseLimit(getProperty(tapConfig, KEY_MAX_OUTPUT_LIMIT), KEY_DEFAULT_OUTPUT_LIMIT, false);
		outputLimitTypes[1] = (LimitUnit)limit[1];

		if (!setMaxOutputLimit((int)limit[0]))
			throw new TAPException("The default output limit (here: " + outputLimits[0] + ") MUST be less or equal to the maximum output limit (here: " + limit[0] + ")!");
	}

	@Override
	public String getProviderName(){
		return providerName;
	}

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

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

	public void setAvailability(final boolean isAvailable){
		this.isAvailable = isAvailable;
	}

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

	public void setDisabilityReason(final String disabilityReason){
		availability = disabilityReason;
	}

	@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<ResultSet>> getOutputFormats(){
		return outputFormats.iterator();
	public OutputFormat<ResultSet> getOutputFormat(final String mimeOrAlias){
		if (mimeOrAlias == null || mimeOrAlias.trim().isEmpty())
			return null;

		for(OutputFormat<ResultSet> f : outputFormats){
			if ((f.getMimeType() != null && f.getMimeType().equalsIgnoreCase(mimeOrAlias)) || (f.getShortMimeType() != null && f.getShortMimeType().equalsIgnoreCase(mimeOrAlias)))
				return f;
		}
	public void addOutputFormat(final OutputFormat<ResultSet> newOutputFormat){
		outputFormats.add(newOutputFormat);
	public boolean removeOutputFormat(final String mimeOrAlias){
		OutputFormat<ResultSet> 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 TAPFactory<ResultSet> getFactory(){
		return tapFactory;
	}

	@Override
	public TAPFileManager getFileManager(){
		return fileManager;
	}

	@Override
	public UserIdentifier getUserIdentifier(){
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean uploadEnabled(){
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public int[] getUploadLimit(){
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public LimitUnit[] getUploadLimitType(){
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int getMaxUploadSize(){
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public TAPMetadata getTAPMetadata(){