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 . * * 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; /** *

General description

* *

An abstract facility to implement the Universal Worker Service pattern.

* *

It can manage several jobs lists (create new, get and remove).

* *

It also interprets {@link HttpServletRequest}, applies the action specified in its given URL and parameters * (according to the IVOA Proposed Recommendation of 2010-02-10) * and returns the corresponding response in a {@link HttpServletResponse}.

* *

The UWS URL interpreter

* *

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.

* *

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).

* *

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)}.

* *

Create a job

* *

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)}.

* *

There are two convenient implementations of this abstract method in {@link BasicUWS} and {@link ExtendedUWS}. These two implementations * are based on the Java Reflection.

* *

UWS actions

* *

All the actions described in the IVOA recommendation are already managed. Each of these actions are defined in * an instance of {@link UWSAction}:

* * *

However you can add your own UWS actions ! 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.

* *

IMPORTANT: 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 !
* 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 !

* *

Note: You can also replace an existing UWS action thanks to the method {@link #replaceUWSAction(UWSAction)} or * {@link #setUWSAction(int, UWSAction)} !

* *

User identification

* *

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.

* *

* Notes: *

*

*

* *

Queue management

* *

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.

* *

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.

* *

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()}.

* *

Serializers & MIME types

* *

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. By default, there are two serializers: {@link XMLSerializer} (the default choice) * and {@link JSONSerializer}.

* *

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.

* *

WARNING: 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.

* *

Note: 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.
* Since the {@link XMLSerializer} is the default format for a UWS resource you can also use the function * {@link UWSService#setXsltURL(String)} !

* *

The UWS Home page

* *

As for a job or a jobs list, a UWS is also a UWS resource. That's why it can also be serialized !

* *

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)}. *

* *

Note: To go back to the UWS serialization (that is to say to abort a call to {@link #setHomePage(String)}), * use the method {@link #setDefaultHomePage()} !

* * * @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. (it is a LinkedHashMap so that jobs lists are ordered by insertion) */ protected final Map mapJobLists; /** The "interpreter" of UWS URLs. */ protected UWSUrl urlInterpreter = null; /** List of available serializers. */ protected final Map 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. (NULL if there is no home page) */ 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 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 */ /* ************ */ /** *

Builds a UWS (the base URI will be extracted at the first request directly from the request itself).

* *

* 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 ! *

* *

note: since no logger is provided, a default one is set automatically (see {@link DefaultUWSLog}).

* * @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 null. * * @see #UWSService(UWSFactory, UWSFileManager, UWSLog) */ public UWSService(final UWSFactory jobFactory, final UWSFileManager fileManager){ this(jobFactory, fileManager, (UWSLog)null); } /** *

Builds a UWS (the base URI will be extracted at the first request directly from the request itself).

* *

* 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 ! *

* * @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 null. */ 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(); // Initialize the list of available serializers: serializers = new HashMap(); addSerializer(new XMLSerializer()); addSerializer(new JSONSerializer()); // Initialize the list of UWS actions: uwsActions = new Vector(); // 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)); } /** *

Builds a UWS with its base UWS URI.

* *

note: since no logger is provided, a default one is set automatically (see {@link DefaultUWSLog}).

* * @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 null 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 null 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 + ")!"); } } /** *

Builds a UWS with the given UWS URL interpreter.

* *

note: since no logger is provided, a default one is set automatically (see {@link DefaultUWSLog}).

* * @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; } /** *

Sets the object used to write/format any error in a HttpServletResponse.

* *

Note: Nothing is done if the given writer is NULL !

* * @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 null. 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(); } /** *

Gets the object which lets extracting the user ID from a HTTP request.

*

note:If the returned user identifier is NULL, no job should have an owner.

* * @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 null). */ 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; } /** *

* 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)}. *

* * @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 true if a redirection to the specified resource must be done * or false 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 null). * @param redirect true if a redirection to the specified resource must be done * or false to copy it. */ public final void setHomePage(URL homePageUrl, boolean redirect){ homePage = homePageUrl.toString(); homeRedirection = redirect; } /** *

Sets the URI of the resource which must be used as home page of this UWS.

* A redirection will always be done on the specified resource. * * @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 true if it is the default home page, false 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."); } /** *

Adds a serializer to this UWS

*

WARNING: 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 !

* * @param serializer The serializer to add. * @return true if the serializer has been successfully added, false 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 true if a serializer exists with the given MIME type, false 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 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 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 null 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 true if the given path/url has been successfully set, false 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 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 true if the jobs list has been successfully added, * false if the given jobs list is null 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 true if the given jobs list has been destroyed, false 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 jlNames = new ArrayList(mapJobLists.keySet()); for(String jlName : jlNames) destroyJobList(jlName); } /* ********************** */ /* UWS ACTIONS MANAGEMENT */ /* ********************** */ /** *

Lets adding the given action to this UWS.

* *

WARNING: 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 !

* * @param action The UWS action to add. * * @return true if the given action has been successfully added, false otherwise. */ public final boolean addUWSAction(UWSAction action){ if (!uwsActions.contains(action)) return uwsActions.add(action); else return false; } /** *

Lets inserting the given action at the given position in the actions list of this UWS.

* * @param indAction The index where the given action must be inserted. * @param action The action to add. * * @return true if the given action has been successfully added, false 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 true if the replacement has been a success, false 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 null if the given action is null * 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 null 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 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 null 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 */ /* ********************** */ /** *

Generate a unique ID for the given request.

* *

By default, a timestamp is returned.

* * @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; } /** *

Executes the given request according to the IVOA Proposed Recommendation of 2010-02-10. * The result is returned in the given response.

* *

Here is the followed algorithm:

*
    *
  1. Load the request in the UWS URL interpreter (see {@link UWSUrl#load(HttpServletRequest)})
  2. *
  3. Extract the user ID (see {@link UserIdentifier#extractUserId(UWSUrl, HttpServletRequest)})
  4. *
  5. 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)})
  6. *
* * @param request The UWS request. * @param response The response of this request which will be edited by the found UWS actions. * * @return true if the request has been executed successfully, false 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; } /** *

Sends a redirection (with the HTTP status code 303) to the given URL/URI into the given response.

* * @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(); } /** *

* 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. *

* * @param error The error to send/display. * @param request The request which has caused the given error (not used by default). * @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); } /** *

* 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. *

* * * @param error The error to send/display. * @param request The request which has caused the given error (not used by default). * @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); } }