View Javadoc

1   /*
2    * Copyright (C) 2017-2019 Centre National d'Etudes Spatiales (CNES).
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 3.0 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   * Lesser General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this library; if not, write to the Free Software
16   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17   * MA 02110-1301  USA
18   */
19  package fr.cnes.doi.resource.mds;
20  
21  import fr.cnes.doi.application.DoiMdsApplication.API_MDS;
22  import fr.cnes.doi.client.ClientMDS;
23  import fr.cnes.doi.client.ClientMDS.DATACITE_API_RESPONSE;
24  import static fr.cnes.doi.client.ClientMDS.POST_DOI;
25  import static fr.cnes.doi.client.ClientMDS.POST_URL;
26  import fr.cnes.doi.exception.ClientMdsException;
27  import fr.cnes.doi.exception.DoiServerException;
28  import static fr.cnes.doi.security.UtilsHeader.SELECTED_ROLE_PARAMETER;
29  import fr.cnes.doi.utils.spec.Requirement;
30  import java.util.Arrays;
31  import java.util.List;
32  import org.apache.logging.log4j.Level;
33  import org.restlet.data.Form;
34  import org.restlet.data.MediaType;
35  import org.restlet.data.Method;
36  import org.restlet.data.Status;
37  import org.restlet.ext.wadl.DocumentationInfo;
38  import org.restlet.ext.wadl.MethodInfo;
39  import org.restlet.ext.wadl.ParameterStyle;
40  import org.restlet.ext.wadl.RepresentationInfo;
41  import org.restlet.resource.Get;
42  import org.restlet.resource.Post;
43  
44  /**
45   * Resource to handle a collection of DOI.
46   *
47   * @author Jean-Christophe Malapert (jean-christophe.malapert@cnes.fr)
48   */
49  public class DoisResource extends BaseMdsResource {
50  
51      /**
52       * Function of this resource {@value #LIST_ALL_DOIS}.
53       */
54      public static final String LIST_ALL_DOIS = "List all DOIs";
55  
56      /**
57       * Function of this resource {@value #CREATE_DOI}.
58       */
59      public static final String CREATE_DOI = "Create a DOI";
60  
61      /**
62       * Init.
63       *
64       * @throws DoiServerException - if a problem happens
65       */
66      @Override
67      protected void doInit() throws DoiServerException {
68          super.doInit();
69          LOG.traceEntry();
70          setDescription("The resource contains the list of DOI and can create a new DOI");
71          LOG.traceExit();
72      }
73  
74      /**
75       * Returns the collection of DOI. This request returns a list of all DOIs
76       * for the requesting datacentre. Status 200 is returned when operation is
77       * successful.
78       *
79       * @return the list of DOI
80       * @throws DoiServerException 204 No Content - no DOIs founds
81       */
82      @Get
83      public List<String> getDois() throws DoiServerException {
84          try {
85              LOG.traceEntry();
86              setStatus(Status.SUCCESS_OK);
87              final List<String> dois = this.getDoiApp().getClient().getDois();
88              if (dois == null || dois.isEmpty()) {
89                  setStatus(Status.SUCCESS_NO_CONTENT);
90              } else {
91                  setStatus(Status.SUCCESS_OK);
92              }
93              return LOG.traceExit(dois);
94          } catch (ClientMdsException ex) {
95                  throw LOG.throwing(
96                          Level.ERROR,
97                          new DoiServerException(getApplication(), API_MDS.DATACITE_PROBLEM, ex)
98                  );
99          }
100     }
101 
102     /**
103      * Creates a new DOI based on the doi and url parameters
104      *
105      * The DOI name is built following this syntax:
106      * <i>CNES_prefix</i>/<i>project_provided_the_DOI_server</i>/project_suffix.
107      *
108      * <p>
109      * The client provides :
110      * <ul>
111      * <li><i>doi</i> : the project suffix, which is an unique identifier within
112      * the project.</li>
113      * <li><i>url</i> : the URL of the landing page.
114      * </ul>
115      * <b>Note 1</b> : the landing page should be accessible from www
116      * <b>Note 2</b> : Metadata must be uploaded first
117      *
118      * <p>
119      * Will mint new DOI if specified DOI doesn't exist. This method will
120      * attempt to update URL if you specify existing DOI. Standard domains and
121      * quota restrictions check will be performed. A Datacentre's doiQuotaUsed
122      * will be increased by 1. A new record in Datasets will be created by
123      * returning a 201 status for operation successful.
124      *
125      * @param doiForm doi and url
126      * @return short explanation of status code e.g. CREATED,
127      * HANDLE_ALREADY_EXISTS etc
128      * @throws DoiServerException - if the response is not a success
129      * <ul>
130      * <li>{@link DATACITE_API_RESPONSE#PROCESS_ERROR}</li>
131      * <li>{@link API_MDS#DATACITE_PROBLEM}</li>
132      * <li>{@link API_MDS#SECURITY_USER_NO_ROLE}</li>
133      * <li>{@link API_MDS#SECURITY_USER_NOT_IN_SELECTED_ROLE}</li>
134      * <li>{@link API_MDS#SECURITY_USER_PERMISSION}</li>
135      * <li>{@link API_MDS#SECURITY_USER_CONFLICT}</li>
136      * </ul>
137      */
138     @Requirement(reqId = Requirement.DOI_SRV_020, reqName = Requirement.DOI_SRV_020_NAME)
139     @Requirement(reqId = Requirement.DOI_SRV_030, reqName = Requirement.DOI_SRV_030_NAME)
140     @Requirement(reqId = Requirement.DOI_MONIT_020, reqName = Requirement.DOI_MONIT_020_NAME)
141     @Requirement(reqId = Requirement.DOI_INTER_070, reqName = Requirement.DOI_INTER_070_NAME)
142     @Requirement(reqId = Requirement.DOI_AUTO_020, reqName = Requirement.DOI_AUTO_020_NAME)
143     @Requirement(reqId = Requirement.DOI_AUTO_030, reqName = Requirement.DOI_AUTO_030_NAME)
144     @Post("form")
145     public String createDoi(final Form doiForm) throws DoiServerException {
146         LOG.traceEntry("Parameters\n\tdoiForm : {}", doiForm);
147         checkInputs(doiForm);
148         final String result;
149         final String selectedRole = extractSelectedRoleFromRequestIfExists();
150         checkPermission(doiForm.getFirstValue(DOI_PARAMETER), selectedRole);
151         try {
152             setStatus(Status.SUCCESS_CREATED, "Operation successful");
153             result = this.getDoiApp().getClient().createDoi(doiForm);
154         } catch (ClientMdsException ex) {
155             if (ex.getStatus().getCode() == Status.CLIENT_ERROR_PRECONDITION_FAILED.getCode()) {
156                 throw LOG.throwing(
157                         Level.ERROR,
158                         new DoiServerException(getApplication(), DATACITE_API_RESPONSE.PROCESS_ERROR,
159                                 ex)
160                 );
161             } else {
162                 throw LOG.throwing(
163                         Level.ERROR,
164                         new DoiServerException(getApplication(), API_MDS.DATACITE_PROBLEM, ex)
165                 );
166             }
167         }
168 
169         return LOG.traceExit(result);
170     }
171 
172     /**
173      * Checks input parameters. Checks that
174      * {@value fr.cnes.doi.resource.mds.BaseMdsResource#DOI_PARAMETER} and
175      * {@value fr.cnes.doi.resource.mds.BaseMdsResource#URL_PARAMETER} are
176      * provided in the mediaForm.
177      *
178      * @param mediaForm the parameters
179      * {@value fr.cnes.doi.resource.mds.BaseMdsResource#DOI_PARAMETER} and
180      * {@value fr.cnes.doi.resource.mds.BaseMdsResource#URL_PARAMETER}
181      * @throws DoiServerException - 400 Bad Request if DOI_PARAMETER and
182      * {@value fr.cnes.doi.resource.mds.BaseMdsResource#URL_PARAMETER} are not
183      * set
184      */
185     @Requirement(reqId = Requirement.DOI_INTER_070, reqName = Requirement.DOI_INTER_070_NAME)
186     private void checkInputs(final Form mediaForm) throws DoiServerException {
187         LOG.traceEntry("Parameter : {}", mediaForm);
188         StringBuilder errorMsg = new StringBuilder();
189         if (isValueNotExist(mediaForm, DOI_PARAMETER)) {
190             errorMsg = errorMsg.append(DOI_PARAMETER).append(" value is not set.");
191         } else {
192             try {
193                 ClientMDS.checkIfAllCharsAreValid(mediaForm.getFirstValue(DOI_PARAMETER));
194             } catch (IllegalArgumentException ex) {
195                 errorMsg = errorMsg.append(DOI_PARAMETER).append(" no valid syntax.");
196             }
197         }
198         if (isValueNotExist(mediaForm, URL_PARAMETER)) {
199             errorMsg = errorMsg.append(URL_PARAMETER).append(" value is not set.");
200         }
201         if (errorMsg.length() == 0) {
202             LOG.debug("The form is valid");
203         } else {
204             throw LOG.throwing(
205                     Level.ERROR,
206                     new DoiServerException(getApplication(), API_MDS.LANGING_PAGE_VALIDATION,
207                             errorMsg.toString())
208             );
209         }
210         LOG.traceExit();
211     }
212 
213     /**
214      * Returns the sucessfull representation.
215      *
216      * @return the Wadl Representation
217      */
218     @Requirement(reqId = Requirement.DOI_DOC_010, reqName = Requirement.DOI_DOC_010_NAME)
219     private RepresentationInfo successFullRepresentation() {
220         final RepresentationInfo repInfo = new RepresentationInfo();
221         repInfo.setMediaType(MediaType.TEXT_URI_LIST);
222         final DocumentationInfo docInfo = new DocumentationInfo();
223         docInfo.setTitle("DOI collection representation");
224         docInfo.setTextContent("This request returns a list of all DOIs for the "
225                 + "requesting datacentre. There is no guaranteed order.");
226         repInfo.setDocumentation(docInfo);
227         return repInfo;
228     }
229 
230     /**
231      * Returns the no content representation.
232      *
233      * @return the Wadl description
234      */
235     @Requirement(reqId = Requirement.DOI_DOC_010, reqName = Requirement.DOI_DOC_010_NAME)
236     private RepresentationInfo noContentRepresentation() {
237         final RepresentationInfo repInfo = new RepresentationInfo();
238         repInfo.setMediaType(MediaType.TEXT_PLAIN);
239         final DocumentationInfo docInfo = new DocumentationInfo();
240         docInfo.setTitle("Empty representation");
241         docInfo.setTextContent("No contain");
242         repInfo.setDocumentation(docInfo);
243         return repInfo;
244     }
245 
246     /**
247      * Returns the exit status representation.
248      *
249      * @return the exit status representation
250      */
251     @Requirement(reqId = Requirement.DOI_DOC_010, reqName = Requirement.DOI_DOC_010_NAME)
252     private RepresentationInfo explainStatusRepresentation() {
253         final RepresentationInfo repInfo = new RepresentationInfo();
254         repInfo.setIdentifier("explainRepresentation");
255         repInfo.setMediaType(MediaType.TEXT_PLAIN);
256         final DocumentationInfo docInfo = new DocumentationInfo();
257         docInfo.setTitle("Explain representation");
258         docInfo.setTextContent("short explanation of status code e.g. CREATED, "
259                 + "HANDLE_ALREADY_EXISTS etc");
260         repInfo.setDocumentation(docInfo);
261         return repInfo;
262     }
263 
264     /**
265      * Describes the GET method. The different representations are the
266      * followings:
267      * <ul>
268      * <li>{@link DATACITE_API_RESPONSE#SUCCESS}</li>
269      * <li>{@link DATACITE_API_RESPONSE#SUCCESS_NO_CONTENT}</li>
270      * <li>{@link API_MDS#DATACITE_PROBLEM}</li>
271      * </ul>
272      *
273      * @param info Wadl description
274      */
275     @Requirement(reqId = Requirement.DOI_DOC_010, reqName = Requirement.DOI_DOC_010_NAME)
276     @Override
277     protected final void describeGet(final MethodInfo info) {
278         info.setName(Method.GET);
279         info.setDocumentation("Retrieves the DOI collection");
280         addResponseDocToMethod(info, createResponseDoc(
281                 DATACITE_API_RESPONSE.SUCCESS.getStatus(),
282                 DATACITE_API_RESPONSE.SUCCESS.getShortMessage(),
283                 successFullRepresentation()));
284         addResponseDocToMethod(info, createResponseDoc(
285                 DATACITE_API_RESPONSE.SUCCESS_NO_CONTENT.getStatus(),
286                 DATACITE_API_RESPONSE.SUCCESS_NO_CONTENT.getShortMessage(),
287                 noContentRepresentation()));
288         addResponseDocToMethod(info, createResponseDoc(
289                 API_MDS.DATACITE_PROBLEM.getStatus(),
290                 API_MDS.DATACITE_PROBLEM.getShortMessage()));
291     }
292 
293     /**
294      * Request representation.
295      *
296      * @return representation
297      */
298     @Requirement(reqId = Requirement.DOI_DOC_010, reqName = Requirement.DOI_DOC_010_NAME)
299     private RepresentationInfo requestRepresentation() {
300         final RepresentationInfo rep = new RepresentationInfo(MediaType.APPLICATION_WWW_FORM);
301         rep.getParameters().add(createQueryParamDoc(
302                 POST_DOI, ParameterStyle.PLAIN, "DOI name", true, "xs:string"));
303         rep.getParameters().add(createQueryParamDoc(
304                 POST_URL, ParameterStyle.PLAIN, "URL of the landing page", true, "xs:string"));
305         return rep;
306     }
307 
308     /**
309      * Describes the POST method. The different representations are the
310      * followings:
311      * <ul>
312      * <li>{@link DATACITE_API_RESPONSE#SUCESS_CREATED}</li>
313      * <li>{@link API_MDS#LANGING_PAGE_VALIDATION}</li>
314      * <li>{@link DATACITE_API_RESPONSE#PROCESS_ERROR}</li>
315      * <li>{@link API_MDS#SECURITY_USER_NO_ROLE}</li>
316      * <li>{@link API_MDS#SECURITY_USER_NOT_IN_SELECTED_ROLE}</li>
317      * <li>{@link API_MDS#SECURITY_USER_PERMISSION}</li>
318      * <li>{@link API_MDS#SECURITY_USER_CONFLICT}</li>
319      * <li>{@link API_MDS#DATACITE_PROBLEM}</li>
320      * </ul>
321      *
322      * @param info Wadl description
323      */
324     @Requirement(reqId = Requirement.DOI_DOC_010, reqName = Requirement.DOI_DOC_010_NAME)
325     @Override
326     protected final void describePost(final MethodInfo info) {
327         info.setName(Method.POST);
328         info.setDocumentation("POST will mint new DOI if specified DOI doesn't exist. "
329                 + "This method will attempt to update URL if you specify existing DOI. "
330                 + "Standard domains and quota restrictions check will be performed. "
331                 + "A Datacentre's doiQuotaUsed will be increased by 1. "
332                 + "A new record in Datasets will be created.");
333 
334         addRequestDocToMethod(info,
335                 Arrays.asList(
336                         createQueryParamDoc(SELECTED_ROLE_PARAMETER,
337                                 ParameterStyle.HEADER,
338                                 "A user can select one role when he is associated "
339                                 + "to several roles", false, "xs:string")
340                 ),
341                 requestRepresentation());
342 
343         addResponseDocToMethod(info, createResponseDoc(
344                 DATACITE_API_RESPONSE.SUCESS_CREATED.getStatus(),
345                 DATACITE_API_RESPONSE.SUCESS_CREATED.getShortMessage(),
346                 explainStatusRepresentation())
347         );
348         addResponseDocToMethod(info, createResponseDoc(
349                 API_MDS.LANGING_PAGE_VALIDATION.getStatus(),
350                 API_MDS.LANGING_PAGE_VALIDATION.getShortMessage(),
351                 "explainRepresentationID")
352         );
353         addResponseDocToMethod(info, createResponseDoc(
354                 DATACITE_API_RESPONSE.PROCESS_ERROR.getStatus(),
355                 DATACITE_API_RESPONSE.PROCESS_ERROR.getShortMessage(),
356                 "explainRepresentationID")
357         );
358         addResponseDocToMethod(info, createResponseDoc(
359                 API_MDS.DATACITE_PROBLEM.getStatus(),
360                 API_MDS.DATACITE_PROBLEM.getShortMessage(),
361                 "explainRepresentationID")
362         );
363         super.describePost(info);
364     }
365 }