Newer
Older
package tap.config;
/*
* 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 2015 - Astronomisches Rechen Institut (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_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_COORD_SYS;
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_LOG_ROTATION;
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_MIN_LOG_LEVEL;
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_TAP_FACTORY;
import static tap.config.TAPConfiguration.KEY_UDFS;
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_ALL;
import static tap.config.TAPConfiguration.VALUE_ANY;
import static tap.config.TAPConfiguration.VALUE_CSV;
import static tap.config.TAPConfiguration.VALUE_DB;
import static tap.config.TAPConfiguration.VALUE_FITS;
import static tap.config.TAPConfiguration.VALUE_HTML;
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_TEXT;
import static tap.config.TAPConfiguration.VALUE_TSV;
import static tap.config.TAPConfiguration.VALUE_VOT;
import static tap.config.TAPConfiguration.VALUE_VOTABLE;
import static tap.config.TAPConfiguration.VALUE_XML;
import static tap.config.TAPConfiguration.fetchClass;
import static tap.config.TAPConfiguration.getProperty;
import static tap.config.TAPConfiguration.isClassName;
import static tap.config.TAPConfiguration.newInstance;
import static tap.config.TAPConfiguration.parseLimit;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
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.db.DBConnection;
import tap.formatter.FITSFormat;
import tap.formatter.HTMLFormat;
gmantele
committed
import tap.formatter.JSONFormat;
import tap.formatter.OutputFormat;
gmantele
committed
import tap.formatter.SVFormat;
import tap.formatter.TextFormat;
gmantele
committed
import tap.formatter.VOTableFormat;
import tap.log.DefaultTAPLog;
import tap.log.TAPLog;
import tap.metadata.TAPMetadata;
import tap.metadata.TableSetParser;
import uk.ac.starlink.votable.DataFormat;
import uk.ac.starlink.votable.VOTableVersion;
import uws.UWSException;
import uws.service.UserIdentifier;
gmantele
committed
import uws.service.file.LocalUWSFileManager;
import uws.service.file.UWSFileManager;
import uws.service.log.UWSLog.LogLevel;
gmantele
committed
import adql.db.FunctionDef;
import adql.db.STCS;
import adql.parser.ParseException;
import adql.query.operand.function.UserDefinedFunction;
/**
*
*
* @author Grégory Mantelet (ARI)
* @version 2.0 (03/2015)
*/
public final class ConfigurableServiceConnection implements ServiceConnection {
gmantele
committed
private UWSFileManager fileManager;
private TAPLog logger;
private TAPFactory tapFactory;
private final TAPMetadata metadata;
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];
gmantele
committed
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> lstCoordSys = 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);
gmantele
committed
public ConfigurableServiceConnection(final Properties tapConfig) throws NullPointerException, TAPException, UWSException{
this(tapConfig, null);
}
public ConfigurableServiceConnection(final Properties tapConfig, final String webAppRootDir) throws NullPointerException, TAPException, UWSException{
if (tapConfig == null)
throw new NullPointerException("Missing TAP properties! ");
// 1. INITIALIZE THE FILE MANAGER:
initFileManager(tapConfig, webAppRootDir);
// 2. CREATE THE LOGGER:
initLogger(tapConfig);
// 3. BUILD THE TAP FACTORY:
initFactory(tapConfig);
// 4. GET THE METADATA:
metadata = initMetadata(tapConfig, webAppRootDir);
// 5. SET ALL GENERAL SERVICE CONNECTION INFORMATION:
providerName = getProperty(tapConfig, KEY_PROVIDER_NAME);
serviceDescription = getProperty(tapConfig, KEY_SERVICE_DESCRIPTION);
initMaxAsyncJobs(tapConfig);
initRetentionPeriod(tapConfig);
initExecutionDuration(tapConfig);
// 6. CONFIGURE OUTPUT:
// default output format = VOTable:
gmantele
committed
outputFormats = new ArrayList<OutputFormat>(1);
// set output formats:
addOutputFormats(tapConfig);
// set output limits:
initOutputLimits(tapConfig);
// 7. CONFIGURE THE UPLOAD:
// 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:
initCoordSys(tapConfig);
initADQLGeometries(tapConfig);
initUDFs(tapConfig);
}
private void initFileManager(final Properties tapConfig, final String webAppRootDir) 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 name 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 = getFile(rootPath, webAppRootDir, KEY_FILE_ROOT_PATH);
// 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{
gmantele
committed
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
fileManager = newInstance(fileManagerType, KEY_FILE_MANAGER, UWSFileManager.class, new Class<?>[]{Properties.class}, new Object[]{tapConfig});
}
protected static final File getFile(final String filePath, final String webAppRootPath, final String propertyName) throws TAPException{
if (filePath == null)
return null;
else if (filePath.startsWith("file:"))
try{
return new File(new URI(filePath));
}catch(URISyntaxException e){
throw new TAPException("Incorrect file URI for the property \"" + propertyName + "\": \"" + filePath + "\"! Bad syntax for the given file URI.", e);
}
else if (filePath.startsWith("/"))
return new File(filePath);
else if (filePath.matches("[a-zA-Z]+:.*"))
throw new TAPException("Incorrect file URI for the property \"" + propertyName + "\": \"" + filePath + "\"! Only URI with the protocol \"file:\" are allowed.");
else
return new File(webAppRootPath, filePath);
}
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
private void initLogger(final Properties tapConfig){
// Create the logger:
logger = new DefaultTAPLog(fileManager);
StringBuffer buf = new StringBuffer("Logger initialized");
// Set the minimum log level:
String propValue = getProperty(tapConfig, KEY_MIN_LOG_LEVEL);
if (propValue != null){
try{
((DefaultTAPLog)logger).setMinLogLevel(LogLevel.valueOf(propValue.toUpperCase()));
}catch(IllegalArgumentException iae){}
}
buf.append(" (minimum log level: ").append(((DefaultTAPLog)logger).getMinLogLevel());
// Set the log rotation period, if any:
if (fileManager instanceof LocalUWSFileManager){
propValue = getProperty(tapConfig, KEY_LOG_ROTATION);
if (propValue != null)
((LocalUWSFileManager)fileManager).setLogRotationFreq(propValue);
buf.append(", log rotation: ").append(((LocalUWSFileManager)fileManager).getLogRotationFreq());
}
// Log the successful initialization with set parameters:
buf.append(").");
logger.info(buf.toString());
}
private void initFactory(final Properties tapConfig) throws TAPException{
String propValue = getProperty(tapConfig, KEY_TAP_FACTORY);
if (propValue == null)
tapFactory = new ConfigurableTAPFactory(this, tapConfig);
else
tapFactory = newInstance(propValue, KEY_TAP_FACTORY, TAPFactory.class, new Class<?>[]{ServiceConnection.class}, new Object[]{this});
}
private TAPMetadata initMetadata(final Properties tapConfig, final String webAppRootDir) 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. Three possible values: " + VALUE_XML + " (to get metadata from a TableSet XML document), " + VALUE_DB + " (to fetch metadata from the database schema TAP_SCHEMA) or the name (between {}) of a class extending TAPMetadata.");
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(getFile(xmlFilePath, webAppRootDir, KEY_METADATA_FILE));
}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);
}
}
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
// MANUAL ~ TAPMETADATA CLASS
else if (isClassName(metaFetchType)){
/* 1. Get the metadata */
// get the class:
Class<? extends TAPMetadata> metaClass = fetchClass(metaFetchType, KEY_METADATA, TAPMetadata.class);
if (metaClass == TAPMetadata.class)
throw new TAPException("Wrong class for the property \"" + KEY_METADATA + "\": \"" + metaClass.getName() + "\"! The class provided in this property MUST EXTEND tap.metadata.TAPMetadata.");
try{
// get one of the expected constructors:
try{
// (UWSFileManager, TAPFactory, TAPLog):
Constructor<? extends TAPMetadata> constructor = metaClass.getConstructor(UWSFileManager.class, TAPFactory.class, TAPLog.class);
// create the TAP metadata:
metadata = constructor.newInstance(fileManager, tapFactory, logger);
}catch(NoSuchMethodException nsme){
// () (empty constructor):
Constructor<? extends TAPMetadata> constructor = metaClass.getConstructor();
// create the TAP metadata:
metadata = constructor.newInstance();
}
}catch(NoSuchMethodException nsme){
throw new TAPException("Missing constructor tap.metadata.TAPMetadata() or tap.metadata.TAPMetadata(uws.service.file.UWSFileManager, tap.TAPFactory, tap.log.TAPLog)! See the value \"" + metaFetchType + "\" of the property \"" + KEY_METADATA + "\".");
}catch(InstantiationException ie){
throw new TAPException("Impossible to create an instance of an abstract class: \"" + metaClass.getName() + "\"! See the value \"" + metaFetchType + "\" of the property \"" + KEY_METADATA + "\".");
}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(Exception ex){
throw new TAPException("Impossible to create an instance of tap.metadata.TAPMetadata as specified in the property \"" + KEY_METADATA + "\": \"" + metaFetchType + "\"!", ex);
}
/* 2. Update the database */
DBConnection conn = null;
try{
conn = tapFactory.getConnection("SET_TAP_SCHEMA");
conn.setTAPSchema(metadata);
}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 = getProperty(tapConfig, KEY_OUTPUT_FORMATS);
// SPECIAL VALUE "ALL":
if (formats == null || formats.equalsIgnoreCase(VALUE_ALL)){
outputFormats.add(new VOTableFormat(this, DataFormat.BINARY));
outputFormats.add(new VOTableFormat(this, DataFormat.BINARY2));
outputFormats.add(new VOTableFormat(this, DataFormat.TABLEDATA));
outputFormats.add(new VOTableFormat(this, DataFormat.FITS));
outputFormats.add(new FITSFormat(this));
outputFormats.add(new SVFormat(this, ",", true));
outputFormats.add(new SVFormat(this, "\t", true));
outputFormats.add(new TextFormat(this));
outputFormats.add(new HTMLFormat(this));
return;
}
// LIST OF FORMATS:
// Since it is a comma separated list of output formats, a loop will parse this list comma by comma:
String f;
int indexSep, indexLPar, indexRPar;
boolean hasVotableFormat = false;
while(formats != null && formats.length() > 0){
// Get a format item from the list:
indexSep = formats.indexOf(',');
// if a comma is after a left parenthesis
indexLPar = formats.indexOf('(');
if (indexSep > 0 && indexLPar > 0 && indexSep > indexLPar){
indexRPar = formats.indexOf(')', indexLPar);
if (indexRPar > 0)
indexSep = formats.indexOf(',', indexRPar);
else
throw new TAPException("Missing right parenthesis in: \"" + formats + "\"!");
}
// 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:
// FITS
if (f.equalsIgnoreCase(VALUE_FITS))
outputFormats.add(new FITSFormat(this));
else if (f.equalsIgnoreCase(VALUE_JSON))
gmantele
committed
outputFormats.add(new JSONFormat(this));
// HTML
else if (f.equalsIgnoreCase(VALUE_HTML))
outputFormats.add(new HTMLFormat(this));
// TEXT
else if (f.equalsIgnoreCase(VALUE_TEXT))
outputFormats.add(new TextFormat(this));
// CSV
else if (f.equalsIgnoreCase(VALUE_CSV))
gmantele
committed
outputFormats.add(new SVFormat(this, ",", true));
// TSV
else if (f.equalsIgnoreCase(VALUE_TSV))
gmantele
committed
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:
gmantele
committed
outputFormats.add(new SVFormat(this, separator, true, mimeType, shortMimeType));
}else
throw new TAPException("Missing separator char/string for the SV output format: \"" + f + "\"!");
}
// VOTABLE
else if (f.toLowerCase().startsWith(VALUE_VOTABLE) || f.toLowerCase().startsWith(VALUE_VOT)){
// Parse the format:
VOTableFormat votFormat = parseVOTableFormat(f);
// Add the VOTable format:
outputFormats.add(votFormat);
// Determine whether the MIME type is the VOTable expected one:
if (votFormat.getShortMimeType().equals("votable") || votFormat.getMimeType().equals("votable"))
hasVotableFormat = true;
}
// custom OutputFormat
else if (isClassName(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);
}
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
// Add by default VOTable format if none is specified:
if (!hasVotableFormat)
outputFormats.add(new VOTableFormat(this));
}
private VOTableFormat parseVOTableFormat(final String propValue) throws TAPException{
DataFormat serialization = null;
VOTableVersion votVersion = null;
String mimeType = null, shortMimeType = null;
// Get the parameters, if any:
int beginSep = propValue.indexOf('(');
if (beginSep > 0){
int endSep = propValue.indexOf(')');
if (endSep <= beginSep)
throw new TAPException("Wrong output format specification syntax in: \"" + propValue + "\"! A VOTable parameters list must end with ')'.");
// split the parameters:
String[] params = propValue.substring(beginSep + 1, endSep).split(",");
if (params.length > 2)
throw new TAPException("Wrong number of parameters for the output format VOTable: \"" + propValue + "\"! Only two parameters may be provided: serialization and version.");
else if (params.length >= 1){
// resolve the serialization format:
params[0] = params[0].trim().toLowerCase();
if (params[0].length() == 0 || params[0].equals("b") || params[0].equals("binary"))
serialization = DataFormat.BINARY;
else if (params[0].equals("b2") || params[0].equals("binary2"))
serialization = DataFormat.BINARY2;
else if (params[0].equals("td") || params[0].equals("tabledata"))
serialization = DataFormat.TABLEDATA;
else if (params[0].equals("fits"))
serialization = DataFormat.FITS;
else
throw new TAPException("Unsupported VOTable serialization: \"" + params[0] + "\"! Accepted values: 'binary' (or 'b'), 'binary2' (or 'b2'), 'tabledata' (or 'td') and 'fits'.");
// resolve the version:
if (params.length == 2){
params[1] = params[1].trim();
if (params[1].equals("1.0") || params[1].equalsIgnoreCase("v1.0"))
votVersion = VOTableVersion.V10;
else if (params[1].equals("1.1") || params[1].equalsIgnoreCase("v1.1"))
votVersion = VOTableVersion.V11;
else if (params[1].equals("1.2") || params[1].equalsIgnoreCase("v1.2"))
votVersion = VOTableVersion.V12;
else if (params[1].equals("1.3") || params[1].equalsIgnoreCase("v1.3"))
votVersion = VOTableVersion.V13;
else
throw new TAPException("Unsupported VOTable version: \"" + params[1] + "\"! Accepted values: '1.0' (or 'v1.0'), '1.1' (or 'v1.1'), '1.2' (or 'v1.2') and '1.3' (or 'v1.3').");
}
}
}
// Get the MIME type and its alias, if any:
beginSep = propValue.indexOf(':');
if (beginSep > 0){
int endSep = propValue.indexOf(':', beginSep + 1);
if (endSep < 0)
endSep = propValue.length();
// extract the MIME type, if any:
mimeType = propValue.substring(beginSep + 1, endSep).trim();
if (mimeType.length() == 0)
mimeType = null;
// extract the short MIME type, if any:
if (endSep < propValue.length()){
beginSep = endSep;
endSep = propValue.indexOf(':', beginSep + 1);
if (endSep >= 0)
throw new TAPException("Wrong output format specification syntax in: \"" + propValue + "\"! After a MIME type and a short MIME type, no more information is expected.");
else
endSep = propValue.length();
shortMimeType = propValue.substring(beginSep + 1, endSep).trim();
if (shortMimeType.length() == 0)
shortMimeType = null;
}
}
// Create the VOTable format:
VOTableFormat votFormat = new VOTableFormat(this, serialization, votVersion);
votFormat.setMimeType(mimeType, shortMimeType);
return votFormat;
}
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
gmantele
committed
setDefaultOutputLimit((Integer)limit[0]);
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
gmantele
committed
if (!setMaxOutputLimit((Integer)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] + ")!");
}
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];
gmantele
committed
setDefaultUploadLimit((Integer)limit[0]);
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];
gmantele
committed
if (!setMaxUploadLimit((Integer)limit[0]))
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);
if (((Integer)limit[0]).intValue() <= 0)
limit[0] = new Integer(TAPConfiguration.DEFAULT_UPLOAD_MAX_FILE_SIZE);
// ...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:
gmantele
committed
int value = (int)((Integer)limit[0] * ((LimitUnit)limit[1]).bytesFactor());
setMaxUploadSize(value);
}
}
private void initUserIdentifier(final Properties tapConfig) throws TAPException{
// Get the property value:
String propValue = getProperty(tapConfig, KEY_USER_IDENTIFIER);
if (propValue != null)
userIdentifier = newInstance(propValue, KEY_USER_IDENTIFIER, UserIdentifier.class);
}
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
private void initCoordSys(final Properties tapConfig) throws TAPException{
// Get the property value:
String propValue = getProperty(tapConfig, KEY_COORD_SYS);
// NO VALUE => ALL COORD SYS ALLOWED!
if (propValue == null)
lstCoordSys = null;
// "NONE" => ALL COORD SYS FORBIDDEN (= no coordinate system expression is allowed)!
else if (propValue.equalsIgnoreCase(VALUE_NONE))
lstCoordSys = new ArrayList<String>(0);
// "ANY" => ALL COORD SYS ALLOWED (= any coordinate system is allowed)!
else if (propValue.equalsIgnoreCase(VALUE_ANY))
lstCoordSys = null;
// OTHERWISE, JUST THE ALLOWED ONE ARE LISTED:
else{
// split all the list items:
String[] items = propValue.split(",");
if (items.length > 0){
lstCoordSys = new ArrayList<String>(items.length);
for(String item : items){
item = item.trim();
// empty item => ignored
if (item.length() <= 0)
continue;
// "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.");
// "ANY" is not allowed inside a list => error!
else if (item.toUpperCase().equals(VALUE_ANY))
throw new TAPException("The special value \"" + VALUE_ANY + "\" can not be used inside a list! It MUST be used in replacement of a whole list to specify that any value is allowed.");
// parse the coordinate system regular expression in order to check it:
else{
try{
STCS.buildCoordSysRegExp(new String[]{item});
lstCoordSys.add(item);
}catch(ParseException pe){
throw new TAPException("Incorrect coordinate system regular expression (\"" + item + "\"): " + pe.getMessage(), pe);
}
}
}
// if finally no item has been specified, consider it as "any coordinate system allowed":
if (lstCoordSys.size() == 0)
lstCoordSys = null;
}else
lstCoordSys = null;
}
}
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);
// "ANY" => ALL FCT ALLOWED (= all of these functions are allowed)!
else if (propValue.equalsIgnoreCase(VALUE_ANY))
geometries = null;
// 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.");
// "ANY" is not allowed inside a list => error!
else if (item.toUpperCase().equals(VALUE_ANY))
throw new TAPException("The special value \"" + VALUE_ANY + "\" can not be used inside a list! It MUST be used in replacement of a whole list to specify that any 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;
}
}
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
private void initUDFs(final Properties tapConfig) throws TAPException{
// Get the property value:
String propValue = getProperty(tapConfig, KEY_UDFS);
// NO VALUE => NO UNKNOWN FCT ALLOWED!
if (propValue == null)
udfs = new ArrayList<FunctionDef>(0);
// "NONE" => NO UNKNOWN FCT ALLOWED (= none of the unknown functions are allowed)!
else if (propValue.equalsIgnoreCase(VALUE_NONE))
udfs = new ArrayList<FunctionDef>(0);
// "ANY" => ALL UNKNOWN FCT ALLOWED (= all of the unknown functions are allowed)!
else if (propValue.equalsIgnoreCase(VALUE_ANY))
udfs = null;
// OTHERWISE, JUST THE ALLOWED ONE ARE LISTED:
else{
char c;
int ind = 0;
short nbComma = 0;
boolean within_item = false, within_params = false, within_classpath = false;
StringBuffer buf = new StringBuffer();
String signature, classpath;
int[] posSignature = new int[]{-1,-1}, posClassPath = new int[]{-1,-1};
signature = null;
classpath = null;
buf.delete(0, buf.length());
while(ind < propValue.length()){
// Get the character:
c = propValue.charAt(ind++);
// If space => ignore
if (!within_params && Character.isWhitespace(c))
continue;
// If inside a parameters list, keep all characters until the list end (')'):
if (within_params){
if (c == ')')
within_params = false;
buf.append(c);
}
// If inside a classpath, keep all characters until the classpath end ('}'):
else if (within_classpath){
if (c == '}')
within_classpath = false;
buf.append(c);
}
// If inside an UDF declaration:
else if (within_item){
switch(c){
case '(': /* start of a parameters list */
within_params = true;
buf.append(c);
break;
case '{': /* start of a class name */
within_classpath = true;
buf.append(c);
break;
case ',': /* separation between the signature and the class name */
// count commas within this item:
if (++nbComma > 1)
// if more than 1, throw an error:
throw new TAPException("Wrong UDF declaration syntax: only two items (signature and class name) can be given within brackets. (position in the property " + KEY_UDFS + ": " + ind + ")");
// end of the signature and start of the class name:
signature = buf.toString();
buf.delete(0, buf.length());
posSignature[1] = ind;
posClassPath[0] = ind + 1;
}
break;
case ']': /* end of a UDF declaration */
within_item = false;
if (nbComma == 0){
signature = buf.toString();
posSignature[1] = ind;
}else{
classpath = (buf.length() == 0 ? null : buf.toString());
if (classpath != null)
posClassPath[1] = ind;
}
buf.delete(0, buf.length());
// no signature...
if (signature == null || signature.length() == 0){
// ...BUT a class name => error
if (classpath != null)
throw new TAPException("Missing UDF declaration! (position in the property " + KEY_UDFS + ": " + posSignature[0] + "-" + posSignature[1] + ")");
// ... => ignore this item
else
continue;
}
// add the new UDF in the list:
try{
// resolve the function signature:
FunctionDef def = FunctionDef.parse(signature);
// resolve the class name:
if (classpath != null){
if (isClassName(classpath)){
Class<? extends UserDefinedFunction> fctClass = null;
try{
// fetch the class:
fctClass = fetchClass(classpath, KEY_UDFS, UserDefinedFunction.class);
// set the class inside the UDF definition:
def.setUDFClass(fctClass);
}catch(TAPException te){
throw new TAPException("Invalid class name for the UDF definition \"" + def + "\": " + te.getMessage() + " (position in the property " + KEY_UDFS + ": " + posClassPath[0] + "-" + posClassPath[1] + ")", te);
}catch(IllegalArgumentException iae){
throw new TAPException("Invalid class name for the UDF definition \"" + def + "\": missing a constructor with a single parameter of type ADQLOperand[] " + (fctClass != null ? "in the class \"" + fctClass.getName() + "\"" : "") + "! (position in the property " + KEY_UDFS + ": " + posClassPath[0] + "-" + posClassPath[1] + ")");
throw new TAPException("Invalid class name for the UDF definition \"" + def + "\": \"" + classpath + "\" is not a class name (or is not surrounding by {} as expected in this property file)! (position in the property " + KEY_UDFS + ": " + posClassPath[0] + "-" + posClassPath[1] + ")");
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
}
// add the UDF:
udfs.add(def);
}catch(ParseException pe){
throw new TAPException("Wrong UDF declaration syntax: " + pe.getMessage() + " (position in the property " + KEY_UDFS + ": " + posSignature[0] + "-" + posSignature[1] + ")", pe);
}
// reset some variables:
nbComma = 0;
signature = null;
classpath = null;
break;
default: /* keep all other characters */
buf.append(c);
break;
}
}
// If outside of everything, just starting a UDF declaration or separate each declaration is allowed:
else{
switch(c){
case '[':
within_item = true;
posSignature[0] = ind + 1;
break;
case ',':
break;
default:
throw new TAPException("Wrong UDF declaration syntax: unexpected character at position " + ind + " in the property " + KEY_UDFS + ": \"" + c + "\"! A UDF declaration must have one of the following syntaxes: \"[signature]\" or \"[signature,{className}]\".");
}
}
}
// If the parsing is not finished, throw an error:
if (within_item)
throw new TAPException("Wrong UDF declaration syntax: missing closing bracket at position " + propValue.length() + "!");
}
}
@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;
}