Select Git revision
UWSService.java
UWSService.java 43.80 KiB
package uws.service;
/*
* This file is part of UWSLibrary.
*
* UWSLibrary 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.
*
* UWSLibrary 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 UWSLibrary. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
* Astronomisches Rechen Institut (ARI)
*/
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Vector;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import uws.AcceptHeader;
import uws.UWSException;
import uws.job.ExecutionPhase;
import uws.job.JobList;
import uws.job.UWSJob;
import uws.job.manager.DefaultExecutionManager;
import uws.job.serializer.JSONSerializer;
import uws.job.serializer.UWSSerializer;
import uws.job.serializer.XMLSerializer;
import uws.job.user.JobOwner;
import uws.service.actions.AddJob;
import uws.service.actions.DestroyJob;
import uws.service.actions.GetJobParam;
import uws.service.actions.JobSummary;
import uws.service.actions.ListJobs;
import uws.service.actions.SetJobParam;
import uws.service.actions.ShowHomePage;
import uws.service.actions.UWSAction;
import uws.service.backup.UWSBackupManager;
import uws.service.error.DefaultUWSErrorWriter;
import uws.service.error.ServiceErrorWriter;
import uws.service.file.UWSFileManager;
import uws.service.log.DefaultUWSLog;
import uws.service.log.UWSLog;
import uws.service.log.UWSLog.LogLevel;
/**
* <h3>General description</h3>
*
* <p>An abstract facility to implement the <b>U</b>niversal <b>W</b>orker <b>S</b>ervice pattern.</p>
*
* <p>It can manage several jobs lists (create new, get and remove).</p>
*
* <p>It also interprets {@link HttpServletRequest}, applies the action specified in its given URL and parameters
* <i>(according to the <a href="http://www.ivoa.net/Documents/UWS/20100210">IVOA Proposed Recommendation of 2010-02-10</a>)</i>
* and returns the corresponding response in a {@link HttpServletResponse}.</p>
*
* <h3>The UWS URL interpreter</h3>
*
* <p>Any subclass of {@link UWSService} has one object called the UWS URL interpreter. It is stored in the field {@link #urlInterpreter}.
* It lets interpreting the URL of any received request. Thus you can know on which jobs list, job and/or job attribute(s)
* the request applies.</p>
*
* <p>This interpreter must be initialized with the base URL/URI of this UWS. By using the default constructor (the one with no parameter),
* the URL interpreter will be built at the first request (see {@link UWSUrl#UWSUrl(HttpServletRequest)}) and so the base URI is
* extracted directly from the request).</p>
*
* <p>You want to set another base URI or to use a custom URL interpreter, you have to set yourself the interpreter
* by using the method {@link #setUrlInterpreter(UWSUrl)}.</p>
*
* <h3>Create a job</h3>
*
* <p>The most important abstract function of this class is {@link UWSService#createJob(Map)}. It allows to create an instance
* of the type of job which is managed by this UWS. The only parameter is a map of a job attributes. It is the same map that
* take the functions {@link UWSJob#UWSJob(Map)} and {@link UWSJob#addOrUpdateParameters(Map)}.</p>
*
* <p>There are two convenient implementations of this abstract method in {@link BasicUWS} and {@link ExtendedUWS}. These two implementations
* are based on the Java Reflection.</p>
*
* <h3>UWS actions</h3>
*
* <p>All the actions described in the IVOA recommendation are already managed. Each of these actions are defined in
* an instance of {@link UWSAction}:</p>
* <ul>
* <li>{@link UWSAction#LIST_JOBS LIST_JOBS}: see the class {@link ListJobs}</li>
* <li>{@link UWSAction#ADD_JOB ADD_JOB}: see the class {@link AddJob}</li>
* <li>{@link UWSAction#DESTROY_JOB DESTROY_JOB}: see the class {@link DestroyJob}</li>
* <li>{@link UWSAction#JOB_SUMMARY JOB_SUMMARY}: see the class {@link JobSummary}</li>
* <li>{@link UWSAction#GET_JOB_PARAM GET_JOB_PARAM}: see the class {@link GetJobParam}</li>
* <li>{@link UWSAction#SET_JOB_PARAM SET_JOB_PARAM}: see the class {@link SetJobParam}</li>
* <li>{@link UWSAction#HOME_PAGE HOME_PAGE}: see the class {@link ShowHomePage}</li>
* </ul>
*
* <p><b>However you can add your own UWS actions !</b> To do that you just need to implement the abstract class {@link UWSAction}
* and to call the method {@link #addUWSAction(UWSAction)} with an instance of this implementation.</p>
*
* <p><b><u>IMPORTANT:</u> You must be careful when you override the function {@link UWSAction#match(UWSUrl, String, HttpServletRequest)}
* so that your test is as precise as possible ! Indeed the order in which the actions of a UWS are evaluated is very important !<br />
* <u>If you want to be sure your custom UWS action is always evaluated before any other UWS action you can use the function
* {@link #addUWSAction(int, UWSAction)} with 0 as first parameter !</u></b></p>
*
* <p><i><u>Note:</u> You can also replace an existing UWS action thanks to the method {@link #replaceUWSAction(UWSAction)} or
* {@link #setUWSAction(int, UWSAction)} !</i></p>
*
* <h3>User identification</h3>
*
* <p>Some UWS actions need to know the current user so that they can adapt their response (i.e. LIST_JOBS must return the jobs of only
* one user: the current one). Thus, before executing a UWS action (and even before choosing the good action in function of the request)
* the function {@link UserIdentifier#extractUserId(UWSUrl, HttpServletRequest)} is called. Its goal
* is to identify the current user in function of the received request.</p>
*
* <p>
* <i><u>Notes:</u>
* <ul>
* <li>If this function returns NULL, the UWS actions must be executed on all jobs, whatever is their owner !</li>
* <li>{@link UserIdentifier} is an interface. So you must implement it and then set its extension to this UWS
* by using {@link #setUserIdentifier(UserIdentifier)}.</li>
* </ul>
* </i></p>
* </p>
*
* <h3>Queue management</h3>
*
* <p>One of the goals of a UWS is to manage an execution queue for all managed jobs. This task is given to an instance
* of {@link DefaultExecutionManager}, stored in the field {@link #executionManager}. Each time a job is created,
* the UWS sets it the execution manager (see {@link AddJob}). Thus the {@link UWSJob#start()} method will ask to the manager
* whether it can execute now or whether it must be put in a {@link ExecutionPhase#QUEUED QUEUED} phase until enough resources are available for its execution.</p>
*
* <p>By extending the class {@link DefaultExecutionManager} and by overriding {@link DefaultExecutionManager#isReadyForExecution(UWSJob)}
* you can change the condition which puts a job in the {@link ExecutionPhase#EXECUTING EXECUTING} or in the {@link ExecutionPhase#QUEUED QUEUED} phase. By default, a job is put
* in the {@link ExecutionPhase#QUEUED QUEUED} phase if there are more running jobs than a given number.</p>
*
* <p>With this manager it is also possible to list all running jobs in addition of all queued jobs, thanks to the methods:
* {@link DefaultExecutionManager#getRunningJobs()}, {@link DefaultExecutionManager#getQueuedJobs()}, {@link DefaultExecutionManager#getNbRunningJobs()}
* and {@link DefaultExecutionManager#getNbQueuedJobs()}.</p>
*
* <h3>Serializers & MIME types</h3>
*
* <p>According to the IVOA recommendation, the XML format is the default format in which each UWS resource must be returned. However it
* is told that other formats can also be managed. To allow that, {@link UWSService} manages a list of {@link UWSSerializer} and
* lets define which is the default one to use. <i>By default, there are two serializers: {@link XMLSerializer} (the default choice)
* and {@link JSONSerializer}.</i></p>
*
* <p>One proposed way to choose automatically the format to use is to look at the Accept header of a HTTP-Request. This header field is
* a list of MIME types (each one with a quality - a sort of priority). Thus each {@link UWSSerializer} is associated with a MIME type so
* that {@link UWSService} can choose automatically the preferred format and so, the serializer to use.</p>
*
* <p><b><u>WARNING:</u> Only one {@link UWSSerializer} can be associated with a given MIME type in an {@link UWSService} instance !
* Thus, if you add a {@link UWSSerializer} to a UWS, and this UWS has already a serializer for the same MIME type,
* it will be replaced by the added one.</b></p>
*
* <p><i><u>Note:</u> A XML document can be linked to a XSLT style-sheet. By using the method {@link XMLSerializer#setXSLTPath(String)}
* you can define the path/URL of the XLST to link to each UWS resource. <br />
* <u>Since the {@link XMLSerializer} is the default format for a UWS resource you can also use the function
* {@link UWSService#setXsltURL(String)} !</u></i></p>
*
* <h3>The UWS Home page</h3>
*
* <p>As for a job or a jobs list, a UWS is also a UWS resource. That's why it can also be serialized !</p>
*
* <p>However in some cases it could more interesting to substitute this resource by a home page of the whole UWS by using the function:
* {@link #setHomePage(String)} or {@link #setHomePage(URL, boolean)}.
* </p>
*
* <p><i><u>Note:</u> To go back to the UWS serialization (that is to say to abort a call to {@link #setHomePage(String)}),
* use the method {@link #setDefaultHomePage()} !</i></p>
*
*
* @author Grégory Mantelet (CDS;ARI)
* @version 4.1 (09/2014)
*/
public class UWSService implements UWS {
/** Name of this UWS. */
protected String name = null;
/** Description of this UWS. */
protected String description = null;
/** List of all managed jobs lists. <i>(it is a LinkedHashMap so that jobs lists are ordered by insertion)</i> */
protected final Map<String,JobList> mapJobLists;
/** The "interpreter" of UWS URLs. */
protected UWSUrl urlInterpreter = null;
/** List of available serializers. */
protected final Map<String,UWSSerializer> serializers;
/** The MIME type of the default serialization format. */
protected String defaultSerializer = null;
/** The serializer chosen during the last call of {@link #executeRequest(HttpServletRequest, HttpServletResponse)}. */
protected UWSSerializer choosenSerializer = null;
/** URL of the home page. (<i>NULL if there is no home page</i>) */
protected String homePage = null;
/** Indicates whether the home page must be a copy or a redirection to the given URL. */
protected boolean homeRedirection = false;
/** List of UWS actions (i.e. to list jobs, to get a job, to set a job parameter, etc...). */
protected final Vector<UWSAction> uwsActions;
/** The action executed during the last call of {@link #executeRequest(HttpServletRequest, HttpServletResponse)}. */
protected UWSAction executedAction = null;
/** The object to use to extract the user ID from the received request. */
protected UserIdentifier userIdentifier = null;
/** Factory which lets creating the UWS jobs and their thread. */
protected final UWSFactory factory;
/** Lets managing all UWS files (i.e. log, result, backup, ...). */
protected final UWSFileManager fileManager;
/** Lets saving and/or restoring the whole UWS. */
protected UWSBackupManager backupManager;
/** Lets logging info/debug/warnings/errors about this UWS. */
protected UWSLog logger;
/** Lets writing/formatting any exception/throwable in a HttpServletResponse. */
protected ServiceErrorWriter errorWriter;
/** Last generated request ID. If the next generated request ID is equivalent to this one,
* a new one will generate in order to ensure the unicity.
* @since 4.1 */
protected static String lastRequestID = null;
/* ************ */
/* CONSTRUCTORS */
/* ************ */
/**
* <p>Builds a UWS (the base URI will be extracted at the first request directly from the request itself).</p>
*
* <p>
* By default, this UWS has 2 serialization formats: XML ({@link XMLSerializer}) and JSON ({@link JSONSerializer}).
* All the default actions of a UWS are also already implemented.
* However, you still have to create at least one job list !
* </p>
*
* <p><i><u>note:</u> since no logger is provided, a default one is set automatically (see {@link DefaultUWSLog}).</i></p>
*
* @param jobFactory Object which lets creating the UWS jobs managed by this UWS and their thread/task.
* @param fileManager Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
*
* @throws NullPointerException If at least one of the parameters is <i>null</i>.
*
* @see #UWSService(UWSFactory, UWSFileManager, UWSLog)
*/
public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager){
this(jobFactory, fileManager, (UWSLog)null);
}
/**
* <p>Builds a UWS (the base URI will be extracted at the first request directly from the request itself).</p>
*
* <p>
* By default, this UWS has 2 serialization formats: XML ({@link XMLSerializer}) and JSON ({@link JSONSerializer}).
* All the default actions of a UWS are also already implemented.
* However, you still have to create at least one job list !
* </p>
*
* @param jobFactory Object which lets creating the UWS jobs managed by this UWS and their thread/task.
* @param fileManager Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
* @param logger Object which lets printing any message (error, info, debug, warning).
*
* @throws NullPointerException If at least one of the parameters is <i>null</i>.
*/
public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSLog logger){
if (jobFactory == null)
throw new NullPointerException("Missing UWS factory! Can not create a UWSService.");
factory = jobFactory;
if (fileManager == null)
throw new NullPointerException("Missing UWS file manager! Can not create a UWSService.");
this.fileManager = fileManager;
this.logger = (logger == null) ? new DefaultUWSLog(this) : logger;
errorWriter = new DefaultUWSErrorWriter(this.logger);
// Initialize the list of jobs:
mapJobLists = new LinkedHashMap<String,JobList>();
// Initialize the list of available serializers:
serializers = new HashMap<String,UWSSerializer>();
addSerializer(new XMLSerializer());
addSerializer(new JSONSerializer());
// Initialize the list of UWS actions:
uwsActions = new Vector<UWSAction>();
// Load the default UWS actions:
uwsActions.add(new ShowHomePage(this));
uwsActions.add(new ListJobs(this));
uwsActions.add(new AddJob(this));
uwsActions.add(new DestroyJob(this));
uwsActions.add(new JobSummary(this));
uwsActions.add(new GetJobParam(this));
uwsActions.add(new SetJobParam(this));
}
/**
* <p>Builds a UWS with its base UWS URI.</p>
*
* <p><i><u>note:</u> since no logger is provided, a default one is set automatically (see {@link DefaultUWSLog}).</i></p>
*
* @param jobFactory Object which lets creating the UWS jobs managed by this UWS and their thread/task.
* @param fileManager Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
* @param baseURI Base UWS URI.
*
* @throws UWSException If the given URI is <i>null</i> or empty.
*
* @see #UWSService(UWSFactory, UWSFileManager, UWSLog, String)
*/
public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final String baseURI) throws UWSException{
this(jobFactory, fileManager, null, baseURI);
}
/**
* Builds a UWS with its base UWS URI.
*
* @param jobFactory Object which lets creating the UWS jobs managed by this UWS and their thread/task.
* @param fileManager Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
* @param logger Object which lets printing any message (error, info, debug, warning).
* @param baseURI Base UWS URI.
*
* @throws UWSException If the given URI is <i>null</i> or empty.
*
* @see UWSUrl#UWSUrl(String)
*/
public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSLog logger, final String baseURI) throws UWSException{
this(jobFactory, fileManager, logger);
// Extract the name of the UWS:
try{
// Set the URL interpreter:
urlInterpreter = new UWSUrl(baseURI);
// ...and the name of this service:
name = urlInterpreter.getUWSName();
// Log the successful initialization:
logger.logUWS(LogLevel.INFO, this, "INIT", "UWS successfully initialized!", null);
}catch(NullPointerException ex){
// Log the exception:
// (since the first constructor has already been called successfully, the logger is now NOT NULL):
logger.logUWS(LogLevel.FATAL, null, "INIT", "Invalid base UWS URI: " + baseURI + "! You should check the configuration of the service.", ex);
// Throw a new UWSException with a more understandable message:
throw new UWSException(UWSException.BAD_REQUEST, ex, "Invalid base UWS URI (" + baseURI + ")!");
}
}
/**
* <p>Builds a UWS with the given UWS URL interpreter.</p>
*
* <p><i><u>note:</u> since no logger is provided, a default one is set automatically (see {@link DefaultUWSLog}).</i></p>
*
* @param jobFactory Object which lets creating the UWS jobs managed by this UWS and their thread/task.
* @param fileManager Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
* @param urlInterpreter The UWS URL interpreter to use in this UWS.
*
* @see #UWSService(UWSFactory, UWSFileManager, UWSLog, UWSUrl)
*/
public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSUrl urlInterpreter){
this(jobFactory, fileManager, null, urlInterpreter);
}
/**
* Builds a UWS with the given UWS URL interpreter.
*
* @param jobFactory Object which lets creating the UWS jobs managed by this UWS and their thread/task.
* @param fileManager Object which lets managing all files managed by this UWS (i.e. log, result, backup, error, ...).
* @param logger Object which lets printing any message (error, info, debug, warning).
* @param urlInterpreter The UWS URL interpreter to use in this UWS.
*/
public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSLog logger, final UWSUrl urlInterpreter){
this(jobFactory, fileManager, logger);
setUrlInterpreter(urlInterpreter);
if (this.urlInterpreter != null)
logger.logUWS(LogLevel.INFO, this, "INIT", "UWS successfully initialized.", null);
}
/* ************** */
/* LOG MANAGEMENT */
/* ************** */
@Override
public UWSLog getLogger(){
return logger;
}
/**
* Gets the object used to write/format any error in a HttpServletResponse.
*
* @return The error writer/formatter.
*/
public final ServiceErrorWriter getErrorWriter(){
return errorWriter;
}
/**
* <p>Sets the object used to write/format any error in a HttpServletResponse.</p>
*
* <p><i><u>Note:</u> Nothing is done if the given writer is NULL !</i></p>
*
* @param errorWriter The new error writer/formatter.
*/
public final void setErrorWriter(ServiceErrorWriter errorWriter){
if (errorWriter != null)
this.errorWriter = errorWriter;
}
/* ***************** */
/* GETTERS & SETTERS */
/* ***************** */
@Override
public final String getName(){
return name;
}
/**
* Sets the name of this UWS.
*
* @param name Its new name.
*/
public final void setName(String name){
this.name = name;
}
@Override
public final String getDescription(){
return description;
}
/**
* Sets the description of this UWS.
*
* @param description Its new description.
*/
public final void setDescription(String description){
this.description = description;
}
/**
* Gets the base UWS URL.
*
* @return The base UWS URL.
*
* @see UWSUrl#getBaseURI()
*/
public final String getBaseURI(){
return (urlInterpreter == null) ? null : urlInterpreter.getBaseURI();
}
@Override
public final UWSUrl getUrlInterpreter(){
return urlInterpreter;
}
/**
* Sets the UWS URL interpreter to use in this UWS.
*
* @param urlInterpreter Its new UWS URL interpreter (may be <i>null</i>. In this case, it will be created from the next request ; see {@link #executeRequest(HttpServletRequest, HttpServletResponse)}).
*/
public final void setUrlInterpreter(UWSUrl urlInterpreter){
this.urlInterpreter = urlInterpreter;
if (name == null && urlInterpreter != null)
name = urlInterpreter.getUWSName();
}
/**
* <p>Gets the object which lets extracting the user ID from a HTTP request.</p>
* <p><i><u>note:</u>If the returned user identifier is NULL, no job should have an owner.</i></p>
*
* @return The used UserIdentifier (MAY BE NULL).
*/
@Override
public final UserIdentifier getUserIdentifier(){
return userIdentifier;
}
/**
* Sets the object which lets extracting the use ID from a received request.
*
* @param identifier The UserIdentifier to use (may be <i>null</i>).
*/
public final void setUserIdentifier(UserIdentifier identifier){
userIdentifier = identifier;
}
@Override
public final UWSFactory getFactory(){
return factory;
}
@Override
public final UWSFileManager getFileManager(){
return fileManager;
}
@Override
public final UWSBackupManager getBackupManager(){
return backupManager;
}
/**
* <p>
* Sets its backup manager.
* This manager will be called at each user action to save only its own jobs list by calling {@link UWSBackupManager#saveOwner(String)}.
* </p>
*
* @param backupManager Its new backup manager.
*/
public final void setBackupManager(final UWSBackupManager backupManager){
this.backupManager = backupManager;
}
/* ******************** */
/* HOME PAGE MANAGEMENT */
/* ******************** */
/**
* Gets the URL of the resource which must be used as home page of this UWS.
*
* @return The URL of the home page.
*/
public final String getHomePage(){
return homePage;
}
/**
* Tells whether a redirection to the specified home page must be done or not.
*
* @return <i>true</i> if a redirection to the specified resource must be done
* or <i>false</i> to copy it.
*/
public final boolean isHomePageRedirection(){
return homeRedirection;
}
/**
* Sets the URL of the resource which must be used as home page of this UWS.
*
* @param homePageUrl The URL of the home page (may be <i>null</i>).
* @param redirect <i>true</i> if a redirection to the specified resource must be done
* or <i>false</i> to copy it.
*/
public final void setHomePage(URL homePageUrl, boolean redirect){
homePage = homePageUrl.toString();
homeRedirection = redirect;
}
/**
* <p>Sets the URI of the resource which must be used as home page of this UWS.</p>
* <i>A redirection will always be done on the specified resource.</i>
*
* @param homePageURI The URL of the home page.
*/
public final void setHomePage(String homePageURI){
homePage = homePageURI;
homeRedirection = true;
}
/**
* Indicates whether the current home page is the default one (the UWS serialization)
* or if it has been specified manually using {@link UWSService#setHomePage(URL, boolean)}.
*
* @return <i>true</i> if it is the default home page, <i>false</i> otherwise.
*/
public final boolean isDefaultHomePage(){
return homePage == null;
}
/**
* Forgets the home page specified by using {@link UWSService#setHomePage(URL, boolean)} - if any -
* and go back to the default home page (XML format).
*/
public final void setDefaultHomePage(){
homePage = null;
homeRedirection = false;
}
/* ********************** */
/* SERIALIZERS MANAGEMENT */
/* ********************** */
/**
* Gets the MIME type of the serializer to use by default.
*
* @return The MIME type of the default serializer.
*/
public final String getDefaultSerializer(){
return defaultSerializer;
}
/**
* Sets the MIME of the serializer to use by default.
*
* @param mimeType The MIME type (only one).
*
* @throws UWSException If there is no serializer with this MIME type available in this UWS.
*/
public final void setDefaultSerializer(String mimeType) throws UWSException{
if (serializers.containsKey(mimeType))
defaultSerializer = mimeType;
else
throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Missing UWS serializer for the MIME types: " + mimeType + "! The default serializer won't be set.");
}
/**
* <p>Adds a serializer to this UWS</p>
* <p><b><u>WARNING:</u> If there is already a serializer with the same MIME type (see {@link UWSSerializer#getMimeType()}) in this UWS ,
* it should be replaced by the given one !</b></p>
*
* @param serializer The serializer to add.
* @return <i>true</i> if the serializer has been successfully added, <i>false</i> otherwise.
*/
public final boolean addSerializer(UWSSerializer serializer){
if (serializer != null){
serializers.put(serializer.getMimeType(), serializer);
if (serializers.size() == 1)
defaultSerializer = serializer.getMimeType();
return true;
}
return false;
}
/**
* Tells whether this UWS has already a serializer with the given MIME type.
*
* @param mimeType A MIME type (only one).
*
* @return <i>true</i> if a serializer exists with the given MIME type, <i>false</i> otherwise.
*/
public final boolean hasSerializerFor(String mimeType){
return serializers.containsKey(mimeType);
}
/**
* Gets the total number of serializers available in this UWS.
*
* @return The number of its serializers.
*/
public final int getNbSerializers(){
return serializers.size();
}
/**
* Gets an iterator of the list of all serializers available in this UWS.
*
* @return An iterator on its serializers.
*/
public final Iterator<UWSSerializer> getSerializers(){
return serializers.values().iterator();
}
@Override
public final UWSSerializer getSerializer(String mimeTypes) throws UWSException{
choosenSerializer = null;
if (mimeTypes != null){
// Parse the given MIME types list:
AcceptHeader accept = new AcceptHeader(mimeTypes);
ArrayList<String> lstMimeTypes = accept.getOrderedMimeTypes();
// Try each of them and stop at the first which match with an existing serializer:
for(int i = 0; choosenSerializer == null && i < lstMimeTypes.size(); i++)
choosenSerializer = serializers.get(lstMimeTypes.get(i));
}
// If no serializer has been found for each given mime type, return the default one:
if (choosenSerializer == null){
choosenSerializer = serializers.get(defaultSerializer);
if (choosenSerializer == null)
throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "No UWS Serializer available neither for \"" + mimeTypes + "\" (given MIME types) nor \"" + defaultSerializer + "\" (default serializer MIME type) !");
}
return choosenSerializer;
}
/**
* Gets the serializer choosen during the last call of {@link #getSerializer(String)}.
*
* @return The last used serializer.
*/
public final UWSSerializer getChoosenSerializer(){
return choosenSerializer;
}
/**
* Removes the serializer whose the MIME type is the same as the given one.
*
* @param mimeType MIME type of the serializer to remove.
* @return The removed serializer
* or <i>null</i> if no corresponding serializer has been found.
*/
public final UWSSerializer removeSerializer(String mimeType){
return serializers.remove(mimeType);
}
/**
* Gets the URL of the XSLT style-sheet that the XML serializer of this UWS is using.
*
* @return The used XSLT URL.
*/
public final String getXsltURL(){
XMLSerializer serializer = (XMLSerializer)serializers.get(UWSSerializer.MIME_TYPE_XML);
if (serializer != null)
return serializer.getXSLTPath();
return null;
}
/**
* Sets the URL of the XSLT style-sheet that the XML serializer of this UWS must use.
*
* @param xsltPath The new XSLT URL.
*
* @return <i>true</i> if the given path/url has been successfully set, <i>false</i> otherwise.
*/
public final boolean setXsltURL(String xsltPath){
XMLSerializer serializer = (XMLSerializer)serializers.get(UWSSerializer.MIME_TYPE_XML);
if (serializer != null){
serializer.setXSLTPath(xsltPath);
return true;
}
return false;
}
/* ********************* */
/* JOBS LISTS MANAGEMENT */
/* ********************* */
/**
* An iterator on the jobs lists list.
*
* @see java.lang.Iterable#iterator()
*/
@Override
public final Iterator<JobList> iterator(){
return mapJobLists.values().iterator();
}
@Override
public final JobList getJobList(String name){
return mapJobLists.get(name);
}
@Override
public final int getNbJobList(){
return mapJobLists.size();
}
/**
* Adds a jobs list to this UWS.
*
* @param jl The jobs list to add.
*
* @return <i>true</i> if the jobs list has been successfully added,
* <i>false</i> if the given jobs list is <i>null</i> or if a jobs list with this name already exists
* or if a UWS is already associated with another UWS.
*
* @see JobList#setUWS(AbstractUWS)
* @see UWS#addJobList(JobList)
*/
@Override
public final boolean addJobList(JobList jl){
if (jl == null)
return false;
else if (mapJobLists.containsKey(jl.getName()))
return false;
try{
jl.setUWS(this);
mapJobLists.put(jl.getName(), jl);
}catch(IllegalStateException ise){
logger.logUWS(LogLevel.ERROR, jl, "ADD_JOB_LIST", "The jobs list \"" + jl.getName() + "\" can not be added into the UWS " + getName() + ": it may already be associated with one!", ise);
return false;
}
return true;
}
@Override
public final boolean destroyJobList(String name){
return destroyJobList(mapJobLists.get(name));
}
/**
* Destroys the given jobs list.
*
* @param jl The jobs list to destroy.
*
* @return <i>true</i> if the given jobs list has been destroyed, <i>false</i> otherwise.
*
* @see JobList#clear()
* @see JobList#setUWS(UWSService)
*/
public boolean destroyJobList(JobList jl){
if (jl == null)
return false;
jl = mapJobLists.remove(jl.getName());
if (jl != null){
try{
jl.clear();
jl.setUWS(null);
}catch(IllegalStateException ise){
logger.logUWS(LogLevel.WARNING, jl, "DESTROY_JOB_LIST", "Impossible to erase completely the association between the jobs list \"" + jl.getName() + "\" and the UWS \"" + getName() + "\"!", ise);
}
}
return jl != null;
}
/**
* Destroys all managed jobs lists.
*
* @see #destroyJobList(String)
*/
public final void destroyAllJobLists(){
ArrayList<String> jlNames = new ArrayList<String>(mapJobLists.keySet());
for(String jlName : jlNames)
destroyJobList(jlName);
}
/* ********************** */
/* UWS ACTIONS MANAGEMENT */
/* ********************** */
/**
* <p>Lets adding the given action to this UWS.</p>
*
* <p><b><u>WARNING:</u> The action will be added at the end of the actions list of this UWS. That means, it will be evaluated (call of
* the method {@link UWSAction#match(UWSUrl, String, HttpServletRequest)}) lastly !</b></p>
*
* @param action The UWS action to add.
*
* @return <i>true</i> if the given action has been successfully added, <i>false</i> otherwise.
*/
public final boolean addUWSAction(UWSAction action){
if (!uwsActions.contains(action))
return uwsActions.add(action);
else
return false;
}
/**
* <p>Lets inserting the given action at the given position in the actions list of this UWS.</p>
*
* @param indAction The index where the given action must be inserted.
* @param action The action to add.
*
* @return <i>true</i> if the given action has been successfully added, <i>false</i> otherwise.
*
* @throws ArrayIndexOutOfBoundsException If the given index is incorrect (index < 0 || index >= uwsActions.size()).
*/
public final boolean addUWSAction(int indAction, UWSAction action) throws ArrayIndexOutOfBoundsException{
if (!uwsActions.contains(action)){
uwsActions.add(indAction, action);
return true;
}
return false;
}
/**
* Replaces the specified action by the given action.
*
* @param indAction Index of the action to replace.
* @param action The replacer.
*
* @return <i>true</i> if the replacement has been a success, <i>false</i> otherwise.
*
* @throws ArrayIndexOutOfBoundsException If the index is incorrect (index < 0 || index >= uwsActions.size()).
*/
public final boolean setUWSAction(int indAction, UWSAction action) throws ArrayIndexOutOfBoundsException{
if (!uwsActions.contains(action)){
uwsActions.set(indAction, action);
return true;
}
return false;
}
/**
* Replaces the action which has the same name that the given action.
*
* @param action The replacer.
*
* @return The replaced action
* or <i>null</i> if the given action is <i>null</i>
* or if there is no action with the same name (in this case, the given action is not added).
*/
public final UWSAction replaceUWSAction(UWSAction action){
if (action == null)
return null;
else{
for(int i = 0; i < uwsActions.size(); i++){
if (uwsActions.get(i).equals(action))
return uwsActions.set(i, action);
}
return null;
}
}
/**
* Gets the number of actions this UWS has.
*
* @return The number of its actions.
*/
public final int getNbUWSActions(){
return uwsActions.size();
}
/**
* Gets the action of this UWS which has the same name as the given one.
*
* @param actionName The name of the searched action.
*
* @return The corresponding action
* or <i>null</i> if there is no corresponding action.
*/
public final UWSAction getUWSAction(String actionName){
for(int i = 0; i < uwsActions.size(); i++){
if (uwsActions.get(i).getName().equals(actionName))
return uwsActions.get(i);
}
return null;
}
/**
* Gets all actions of this UWS.
*
* @return An iterator on its actions.
*/
public final Iterator<UWSAction> getUWSActions(){
return uwsActions.iterator();
}
/**
* Gets the UWS action executed during the last call of {@link #executeRequest(HttpServletRequest, HttpServletResponse)}.
*
* @return The last used UWS action.
*/
public final UWSAction getExecutedAction(){
return executedAction;
}
/**
* Removes the specified action from this UWS.
*
* @param indAction The index of the UWS action to remove.
*
* @return The removed action.
*
* @throws ArrayIndexOutOfBoundsException If the given index is incorrect (index < 0 || index >= uwsActions.size()).
*/
public final UWSAction removeUWSAction(int indAction) throws ArrayIndexOutOfBoundsException{
return uwsActions.remove(indAction);
}
/**
* Removes the action of this UWS which has the same name as the given one.
*
* @param actionName The name of the UWS to remove.
* @return The removed action
* or <i>null</i> if there is no corresponding action.
*/
public final UWSAction removeUWSAction(String actionName){
for(int i = 0; i < uwsActions.size(); i++){
if (uwsActions.get(i).getName().equals(actionName))
return uwsActions.remove(i);
}
return null;
}
/* ********************** */
/* UWS MANAGEMENT METHODS */
/* ********************** */
/**
* <p>Generate a unique ID for the given request.</p>
*
* <p>By default, a timestamp is returned.</p>
*
* @param request Request whose an ID is asked.
*
* @return The ID of the given request.
*
* @since 4.1
*/
protected synchronized String generateRequestID(final HttpServletRequest request){
String id;
do{
id = System.currentTimeMillis() + "";
}while(lastRequestID != null && lastRequestID.startsWith(id));
lastRequestID = id;
return id;
}
/**
* <p>Executes the given request according to the <a href="http://www.ivoa.net/Documents/UWS/20100210/">IVOA Proposed Recommendation of 2010-02-10</a>.
* The result is returned in the given response.</p>
*
* <p>Here is the followed algorithm:</p>
* <ol>
* <li>Load the request in the UWS URL interpreter (see {@link UWSUrl#load(HttpServletRequest)})</li>
* <li>Extract the user ID (see {@link UserIdentifier#extractUserId(UWSUrl, HttpServletRequest)})</li>
* <li>Iterate - in order - on all available actions and apply the first which matches.
* (see {@link UWSAction#match(UWSUrl, String, HttpServletRequest)} and {@link UWSAction#apply(UWSUrl, String, HttpServletRequest, HttpServletResponse)})</li>
* </ol>
*
* @param request The UWS request.
* @param response The response of this request which will be edited by the found UWS actions.
*
* @return <i>true</i> if the request has been executed successfully, <i>false</i> otherwise.
*
* @throws UWSException If no action matches or if any error has occurred while applying the found action.
* @throws IOException If it is impossible to write in the given {@link HttpServletResponse}.
*
* @see UWSUrl#UWSUrl(HttpServletRequest)
* @see UWSUrl#load(HttpServletRequest)
* @see UserIdentifier#extractUserId(UWSUrl, HttpServletRequest)
* @see UWSAction#match(UWSUrl, String, HttpServletRequest)
* @see UWSAction#apply(UWSUrl, String, HttpServletRequest, HttpServletResponse)
*/
public boolean executeRequest(HttpServletRequest request, HttpServletResponse response) throws UWSException, IOException{
if (request == null || response == null)
return false;
// Generate a unique ID for this request execution (for log purpose only):
final String reqID = generateRequestID(request);
// Log the reception of the request:
logger.logHttp(LogLevel.INFO, request, reqID, null, null);
boolean actionApplied = false;
UWSAction action = null;
JobOwner user = null;
try{
if (urlInterpreter == null){
// Initialize the URL interpreter if not already done:
setUrlInterpreter(new UWSUrl(request));
// Log the successful initialization:
logger.logUWS(LogLevel.INFO, this, "INIT", "UWS successfully initialized.", null);
}
// Update the UWS URL interpreter:
urlInterpreter.load(request);
// Identify the user:
user = (userIdentifier == null) ? null : userIdentifier.extractUserId(urlInterpreter, request);
// Apply the appropriate UWS action:
for(int i = 0; action == null && i < uwsActions.size(); i++){
if (uwsActions.get(i).match(urlInterpreter, user, request)){
action = uwsActions.get(i);
choosenSerializer = null;
actionApplied = action.apply(urlInterpreter, user, request, response);
}
}
// If no corresponding action has been found, throw an error:
if (action == null)
throw new UWSException(UWSException.NOT_IMPLEMENTED, "Unknown UWS action!");
response.flushBuffer();
// Log the successful execution of the action:
logger.logHttp(LogLevel.INFO, response, reqID, user, "HTTP " + UWSException.OK + " - Action \"" + ((action != null) ? action.getName() : null) + "\" successfully executed.", null);
}catch(UWSException ex){
// If redirection, flag the action as executed with success:
if (ex.getHttpErrorCode() == UWSException.SEE_OTHER)
actionApplied = true;
sendError(ex, request, reqID, user, ((action != null) ? action.getName() : null), response);
}catch(Exception ex){
sendError(ex, request, reqID, user, ((action != null) ? action.getName() : null), response);
}finally{
executedAction = action;
}
return actionApplied;
}
/**
* <p>Sends a redirection (with the HTTP status code 303) to the given URL/URI into the given response.</p>
*
* @param url The redirection URL/URI.
* @param request The {@link HttpServletRequest} which may be used to make a redirection.
* @param user The user which executes the given request.
* @param uwsAction The UWS action corresponding to the given request.
* @param response The {@link HttpServletResponse} which must contain all information to make a redirection.
*
* @throws IOException If there is an error during the redirection.
* @throws UWSException If there is any other error.
*/
public void redirect(String url, HttpServletRequest request, JobOwner user, String uwsAction, HttpServletResponse response) throws IOException, UWSException{
response.setStatus(HttpServletResponse.SC_SEE_OTHER);
response.setContentType(request.getContentType());
response.setHeader("Location", url);
response.flushBuffer();
}
/**
* <p>
* Fills the response with the given error. The HTTP status code is set in function of the error code of the given UWSException.
* If the error code is {@link UWSException#SEE_OTHER} this method calls {@link #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse)}.
* Otherwise the function {@link HttpServletResponse#sendError(int, String)} is called.
* </p>
*
* @param error The error to send/display.
* @param request The request which has caused the given error <i>(not used by default)</i>.
* @param reqID ID of the request.
* @param user The user which executes the given request.
* @param uwsAction The UWS action corresponding to the given request.
* @param response The response in which the error must be published.
*
* @throws IOException If there is an error when calling {@link #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse)} or {@link HttpServletResponse#sendError(int, String)}.
* @throws UWSException If there is an error when calling {@link #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse))}.
*
* @see #redirect(String, HttpServletRequest, JobOwner, String, HttpServletResponse)
* @see {@link ServiceErrorWriter#writeError(Throwable, HttpServletResponse, HttpServletRequest, JobOwner, String)}
*/
public final void sendError(UWSException error, HttpServletRequest request, String reqID, JobOwner user, String uwsAction, HttpServletResponse response) throws IOException, UWSException{
if (error.getHttpErrorCode() == UWSException.SEE_OTHER){
// Log the redirection, if any:
logger.logHttp(LogLevel.INFO, response, reqID, user, "HTTP " + UWSException.SEE_OTHER + " [Redirection toward " + error.getMessage() + "] - Action \"" + uwsAction + "\" successfully executed.", null);
// Apply the redirection:
redirect(error.getMessage(), request, user, uwsAction, response);
}else
sendError((Exception)error, request, reqID, user, uwsAction, response);
}
/**
* <p>
* Fills the response with the given error.
* The stack trace of the error is printed on the standard output and then the function
* {@link HttpServletResponse#sendError(int, String)} is called with the HTTP status code is {@link UWSException#INTERNAL_SERVER_ERROR}
* and the message of the given exception.
* </p>
*
*
* @param error The error to send/display.
* @param request The request which has caused the given error <i>(not used by default)</i>.
* @param reqID ID of the request.
* @param user The user which executes the given request.
* @param uwsAction The UWS action corresponding to the given request.
* @param response The response in which the error must be published.
*
* @throws IOException If there is an error when calling {@link HttpServletResponse#sendError(int, String)}.
*
* @see {@link ServiceErrorWriter#writeError(Throwable, HttpServletResponse, HttpServletRequest, String, JobOwner, String)}
*/
public final void sendError(Exception error, HttpServletRequest request, String reqID, JobOwner user, String uwsAction, HttpServletResponse response) throws IOException{
// Write the error in the response and return the appropriate HTTP status code:
errorWriter.writeError(error, response, request, reqID, user, uwsAction);
// Log the error:
logger.logHttp(LogLevel.ERROR, response, reqID, user, "HTTP " + response.getStatus() + " - Can not complete the UWS action \"" + uwsAction + "\", because: " + error.getMessage(), error);
}
}