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-2016 - 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.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import uws.AcceptHeader;
import uws.UWSException;
import uws.UWSToolBox;
import uws.job.JobList;
import uws.job.JobThread;
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.SetUWSParameter;
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;
import uws.service.request.RequestParser;
/**
* <p>This class implements directly the interface {@link UWS} and so, it represents the core of a UWS service.</p>
*
* <h3>Usage</h3>
*
* <p>
* Using this class is very simple! An instance must be created by providing at a factory - {@link UWSFactory} - and a file manager - {@link UWSFileManager}.
* This creation must be done in the init() function of a {@link HttpServlet}. Then, still in init(), at least one job list must be created.
* Finally, in order to ensure that all requests are interpreted by the UWS service, they must be sent to the created {@link UWSService} in the function
* {@link #executeRequest(HttpServletRequest, HttpServletResponse)}.
* </p>
* <p>Here is an example of what should look like the servlet class:</p>
* <pre>
* public class MyUWSService extends HttpServlet {
* private UWS uws;
*
* public void init(ServletConfig config) throws ServletException {
* try{
* // Create the UWS service:
* uws = new UWSService(new MyUWSFactory(), new LocalUWSFileManager(new File(config.getServletContext().getRealPath("UWSFiles"))));
* // Create at least one job list (otherwise no job can be started):
* uws.addJobList("jobList");
* }catch(UWSException ue){
* throw new ServletException("Can not initialize the UWS service!", ue);
* }
* }
*
* public void destroy(){
* if (uws != null)
* uws.destroy();
* }
*
* public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException{
* try{
* service.executeRequest(request, response);
* }catch(UWSException ue){
* response.sendError(ue.getHttpErrorCode(), ue.getMessage());
* }
* }
* }
* </pre>
*
* <h3>UWS actions</h3>
*
* <p>
* All standard UWS actions are already implemented in this class. However, it is still possible to modify their implementation and/or to
* add or remove some actions.
* </p>
* <p>
* A UWS action is actually implemented here by a class extending the abstract class {@link UWSAction}. Here is the full list of all
* the available and already implemented actions:
* </p>
* <ul>
* <li>{@link AddJob}</li>
* <li>{@link DestroyJob}</li>
* <li>{@link JobSummary}</li>
* <li>{@link GetJobParam}</li>
* <li>{@link SetJobParam}</li>
* <li>{@link ListJobs}</li>
* </ul>
* <p>
* To add an action, you should use the function {@link #addUWSAction(UWSAction)}, to remove one {@link #removeUWSAction(int)} or {@link #removeUWSAction(String)}.
* Note that this last function takes a String parameter. This parameter is the name of the UWS action to remove. Indeed, each UWS action must have an internal
* name representing the action. Thus, it is possible to replace a UWS action implementation by using the function {@link #replaceUWSAction(UWSAction)} ; this
* function will replace the action having the same name as the given action.
* </p>
*
* <h3>Home page</h3>
*
* <p>
* In addition of all the actions listed above, a last action is automatically added: {@link ShowHomePage}. This is the action which will display the home page of
* the UWS service. It is called when the root resource of the web service is asked. To change it, you can either overwrite this action
* (see {@link #replaceUWSAction(UWSAction)}) or set an home page URL with the function {@link #setHomePage(String)} <i>(the parameter is a URI pointing on either
* a local or a remote resource)</i> or {@link #setHomePage(URL, boolean)}.
* </p>
*
* @author Grégory Mantelet (CDS;ARI)
* @version 4.2 (06/2016)
*/
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;
/** MIME type of the custom home page. By default, it is "text/html".
* @since 4.2 */
protected String homePageMimeType = "text/html";
/** 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 extract all parameters from an HTTP request, whatever is its content-type.
* @since 4.1*/
protected final RequestParser requestParser;
/** 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>.
* @throws UWSException If unable to create a request parser using the factory (see {@link UWSFactory#createRequestParser(UWSFileManager)}).
*
* @see #UWSService(UWSFactory, UWSFileManager, UWSLog)
*/
public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager) throws UWSException{
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>.
* @throws UWSException If unable to create a request parser using the factory (see {@link UWSFactory#createRequestParser(UWSFileManager)}).
*/
public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSLog logger) throws UWSException{
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;
requestParser = jobFactory.createRequestParser(fileManager);
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 SetUWSParameter(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.
*
* @throws UWSException If unable to create a request parser using the factory (see {@link UWSFactory#createRequestParser(UWSFileManager)}).
*
* @see #UWSService(UWSFactory, UWSFileManager, UWSLog, UWSUrl)
*/
public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSUrl urlInterpreter) throws UWSException{
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.
*
* @throws UWSException If unable to create a request parser using the factory (see {@link UWSFactory#createRequestParser(UWSFileManager)}).
*/
public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager, final UWSLog logger, final UWSUrl urlInterpreter) throws UWSException{
this(jobFactory, fileManager, logger);
setUrlInterpreter(urlInterpreter);
if (this.urlInterpreter != null)
logger.logUWS(LogLevel.INFO, this, "INIT", "UWS successfully initialized.", null);
}
@Override
public void destroy(){
// Backup all jobs:
/* Jobs are backuped now so that running jobs are set back to the PENDING phase in the backup.
* Indeed, the "stopAll" operation of the ExecutionManager may fail and would set the phase to ERROR
* for the wrong reason. */
if (backupManager != null){
// save all jobs:
backupManager.setEnabled(true);
backupManager.saveAll();
// stop the automatic backup, if there is one:
backupManager.setEnabled(false);
}
// Stop all jobs and stop watching for the jobs' destruction:
for(JobList jl : mapJobLists.values()){
jl.getExecutionManager().stopAll();
jl.getDestructionManager().stop();
}
// Just in case that previous clean "stop"s did not work, try again an interruption for all running threads:
/* note: timers are not part of this ThreadGroup and so, they won't be affected by this function call. */
JobThread.tg.interrupt();
// Log the service is stopped:
if (logger != null)
logger.logUWS(LogLevel.INFO, this, "STOP", "UWS Service \"" + getName() + "\" stopped!", 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();
if (this.urlInterpreter != null)
this.urlInterpreter.setUwsURI(null);
}
/**
* <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(JobOwner)}.
* </p>
*
* @param backupManager Its new backup manager.
*/
public final void setBackupManager(final UWSBackupManager backupManager){
this.backupManager = backupManager;
}
@Override
public final RequestParser getRequestParser(){
return requestParser;
}
/* ******************** */
/* 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;
}
/**
* <p>Get the MIME type of the custom home page.</p>
*
* <p>By default, it is the same as the default home page: "text/html".</p>
*
* <p><i>Note:
* This function has a sense only if the HOME PAGE resource of this UWS service
* is still the default home page (i.e. {@link ShowHomePage}).
* </i></p>
*
* @return MIME type of the custom home page.
*
* @since 4.2
*/
public final String getHomePageMimeType(){
return homePageMimeType;
}
/**
* <p>Set the MIME type of the custom home page.</p>
*
* <p>A NULL value will be considered as "text/html".</p>
*
* <p><i>Note:
* This function has a sense only if the HOME PAGE resource of this UWS service
* is still the default home page (i.e. {@link ShowHomePage}).
* </i></p>
*
* @param mime MIME type of the custom home page.
*
* @since 4.2
*/
public final void setHomePageMimeType(final String mime){
homePageMimeType = (mime == null || mime.trim().length() == 0) ? "text/html" : mime.trim();
}
/* ********************** */
/* 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(UWS)
* @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(UWS)
*/
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, JobOwner, 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, JobOwner, HttpServletRequest)} and {@link UWSAction#apply(UWSUrl, JobOwner, 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, JobOwner, HttpServletRequest)
* @see UWSAction#apply(UWSUrl, JobOwner, 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 = (request.getAttribute(UWS.REQ_ATTRIBUTE_ID) == null ? generateRequestID(request) : request.getAttribute(UWS.REQ_ATTRIBUTE_ID).toString());
if (request.getAttribute(UWS.REQ_ATTRIBUTE_ID) == null)
request.setAttribute(UWS.REQ_ATTRIBUTE_ID, reqID);
// Extract all parameters:
if (request.getAttribute(UWS.REQ_ATTRIBUTE_PARAMETERS) == null){
try{
request.setAttribute(UWS.REQ_ATTRIBUTE_PARAMETERS, requestParser.parse(request));
}catch(UWSException ue){
logger.log(LogLevel.ERROR, "REQUEST_PARSER", "Can not extract the HTTP request parameters!", ue);
}
}
// 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 (this.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:
UWSUrl urlInterpreter = new UWSUrl(this.urlInterpreter);
urlInterpreter.load(request);
// Identify the user:
user = UWSToolBox.getUser(request, userIdentifier);
// Set the character encoding:
response.setCharacterEncoding(UWSToolBox.DEFAULT_CHAR_ENCODING);
// 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, "UWS action \"" + ((action != null) ? action.getName() : null) + "\" successfully executed.", null);
}catch(IOException ioe){
/*
* Any IOException thrown while writing the HTTP response is generally caused by a client abortion (intentional or timeout)
* or by a connection closed with the client for another reason.
* Consequently, a such error should not be considered as a real error from the server or the library: the request is
* canceled, and so the response is not expected. It is anyway not possible any more to send it (header and/or body) totally
* or partially.
* Nothing can solve this error. So the "error" is just reported as a simple information and theoretically the action
* executed when this error has been thrown is already stopped.
*/
logger.logHttp(LogLevel.INFO, response, reqID, user, "HTTP request aborted or connection with the client closed => the UWS action \"" + action.getName() + "\" has stopped and the body of the HTTP response can not have been partially or completely written!", null);
}catch(UWSException ex){
/*
* Any known/"expected" UWS exception is logged but also returned to the HTTP client in an error document.
* Since the error is known, it is supposed to have already been logged with a full stack trace. Thus, there
* is no need to log again its stack trace...just its message is logged.
* Besides, this error may also be just a redirection and not a true error. In such case, the error message
* is not logged.
*/
// 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(IllegalStateException ise){
/*
* Any IllegalStateException that reaches this point, is supposed coming from a HttpServletResponse operation which
* has to reset the response buffer (e.g. resetBuffer(), sendRedirect(), sendError()).
* If this exception happens, the library tried to rewrite the HTTP response body with a message or a result,
* while this body has already been partially sent to the client. It is then no longer possible to change its content.
* Consequently, the error is logged as FATAL and a message will be appended at the end of the already submitted response
* to alert the HTTP client that an error occurs and the response should not be considered as complete and reliable.
*/
// Write the error in the response and return the appropriate HTTP status code:
errorWriter.writeError(ise, response, request, reqID, user, ((action != null) ? action.getName() : null));
// Log the error:
getLogger().logHttp(LogLevel.FATAL, response, reqID, user, "HTTP response already partially committed => the UWS action \"" + action.getName() + "\" has stopped and the body of the HTTP response can not have been partially or completely written!", (ise.getCause() != null) ? ise.getCause() : ise);
}catch(Throwable t){
/*
* Any other error is considered as unexpected if it reaches this point. Consequently, it has not yet been logged.
* So its stack trace will be fully logged, and an appropriate message will be returned to the HTTP client. The
* returned document should contain not too technical information which would be useless for the user.
*/
sendError(t, request, reqID, user, ((action != null) ? action.getName() : null), response);
}finally{
executedAction = action;
// Free resources about uploaded files ; only unused files will be deleted:
UWSToolBox.deleteUploads(request);
}
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.setCharacterEncoding(UWSToolBox.DEFAULT_CHAR_ENCODING);
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 #sendError(Throwable, HttpServletRequest, String, JobOwner, String, HttpServletResponse)
*/
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((Throwable)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 ServiceErrorWriter#writeError(Throwable, HttpServletResponse, HttpServletRequest, String, JobOwner, String)
*/
public final void sendError(Throwable 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:
if (error instanceof UWSException)
logger.logHttp(LogLevel.ERROR, response, reqID, user, "UWS action \"" + uwsAction + "\" FAILED with the error: \"" + error.getMessage() + "\"!", null);
else
logger.logHttp(LogLevel.FATAL, response, reqID, user, "UWS action \"" + uwsAction + "\" execution FAILED with a GRAVE error!", error);
}
}