View Javadoc
1   /* 
2   * Copyright (C) 2003-2015 eXo Platform SAS.
3   *
4   * This program is free software: you can redistribute it and/or modify
5   * it under the terms of the GNU Lesser General Public License as published by
6   * the Free Software Foundation, either version 3 of the License, or
7   * (at your option) any later version.
8   *
9   * This program 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
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program. If not, see http://www.gnu.org/licenses/ .
16  */
17  package org.exoplatform.commons.search.rest;
18  
19  import io.swagger.annotations.*;
20  import org.apache.commons.lang.StringUtils;
21  import org.exoplatform.commons.search.domain.IndexingOperation;
22  import org.exoplatform.commons.search.index.IndexingOperationProcessor;
23  import org.exoplatform.commons.search.index.IndexingService;
24  import org.exoplatform.commons.search.index.IndexingServiceConnector;
25  import org.exoplatform.commons.search.index.impl.QueueIndexingService;
26  import org.exoplatform.commons.search.rest.resource.CollectionResource;
27  import org.exoplatform.commons.search.rest.resource.CollectionSizeResource;
28  import org.exoplatform.commons.search.rest.resource.ConnectorResource;
29  import org.exoplatform.commons.search.rest.resource.OperationResource;
30  import org.exoplatform.common.http.HTTPStatus;
31  import org.exoplatform.services.log.ExoLogger;
32  import org.exoplatform.services.log.Log;
33  import org.exoplatform.services.rest.resource.ResourceContainer;
34  import org.exoplatform.ws.frameworks.json.impl.JsonException;
35  import org.exoplatform.ws.frameworks.json.impl.JsonGeneratorImpl;
36  import org.exoplatform.ws.frameworks.json.value.JsonValue;
37  
38  import javax.annotation.security.RolesAllowed;
39  import javax.ws.rs.*;
40  import javax.ws.rs.core.MediaType;
41  import javax.ws.rs.core.Response;
42  import java.io.Serializable;
43  import java.util.ArrayList;
44  import java.util.Calendar;
45  import java.util.List;
46  
47  /**
48   * Created by The eXo Platform SAS
49   * Author : Thibault Clement
50   * tclement@exoplatform.com
51   * 10/6/15
52   */
53  @Path(IndexingManagementRestServiceV1.BASE_VERSION_URI+ IndexingManagementRestServiceV1.INDEXING_MANAGEMENT_URI)
54  @RolesAllowed("administrators")
55  @Api(
56      value = IndexingManagementRestServiceV1.BASE_VERSION_URI+ IndexingManagementRestServiceV1.INDEXING_MANAGEMENT_URI,
57      description = "Entry point for Indexing Management resources",
58      basePath = IndexingManagementRestServiceV1.BASE_VERSION_URI+ IndexingManagementRestServiceV1.INDEXING_MANAGEMENT_URI
59  )
60  public class IndexingManagementRestServiceV1 implements ResourceContainer {
61  
62    public final static String BASE_VERSION_URI = "/v1";
63    public final static String INDEXING_MANAGEMENT_URI = "/indexingManagement";
64    public final static String CONNECTORS_URI = "/connectors";
65    public final static String OPERATIONS_URI = "/operations";
66    public final static String ERRORS_URI = "/errors";
67  
68    private final static Log LOG = ExoLogger.getLogger(IndexingManagementRestServiceV1.class);
69  
70    private QueueIndexingService indexingService;
71    private IndexingOperationProcessor indexingOperationProcessor;
72  
73    public IndexingManagementRestServiceV1(IndexingService indexingService, IndexingOperationProcessor indexingOperationProcessor) {
74      this.indexingService = (QueueIndexingService) indexingService;
75      this.indexingOperationProcessor = indexingOperationProcessor;
76    }
77  
78    // Indexing Service Connectors
79  
80    @GET
81    @Path(IndexingManagementRestServiceV1.CONNECTORS_URI)
82    @Produces(MediaType.APPLICATION_JSON)
83    @RolesAllowed("administrators")
84    @ApiOperation(value = "Return all Indexing Connectors")
85    @ApiResponses(value = {
86        @ApiResponse(code = 200, message = "Successful retrieval of all Indexing Connectors"),
87        @ApiResponse(code = 500, message = "Can't generate JSON file") })
88    public Response getConnectors(
89        @ApiParam(
90            value = "The name of a JavaScript function to be used as the JSONP callback",
91            required = false)
92        @QueryParam("jsonp")
93        String jsonp,
94        @ApiParam(
95            value = "Tell the service if it must return the size of the collection in the store",
96            required = false)
97        @QueryParam("returnSize")
98        boolean returnSize
99    ) {
100 
101     //Get connectors
102     List<IndexingServiceConnector> connectors = new ArrayList<>(indexingOperationProcessor.getConnectors().values());
103 
104     CollectionResource<IndexingServiceConnector> connectorData;
105 
106     //Manage return size parameter
107     if (returnSize) {
108       int connectorNb = indexingOperationProcessor.getConnectors().size();
109       connectorData = new CollectionSizeResource<>(connectors, connectorNb);
110     }
111     else {
112       connectorData = new CollectionResource<>(connectors);
113     }
114 
115     Response.ResponseBuilder response;
116 
117     //Manage json-callback parameter
118     if (StringUtils.isNotBlank(jsonp)) {
119       try {
120         response = buildJsonCallBack(connectorData, jsonp);
121       } catch (JsonException e) {
122         LOG.error(e);
123         response = Response.status(HTTPStatus.INTERNAL_ERROR);
124       }
125     }
126     else {
127       response = Response.ok(connectorData, MediaType.APPLICATION_JSON);
128     }
129 
130     return response.build();
131   }
132 
133   @GET
134   @Path(IndexingManagementRestServiceV1.CONNECTORS_URI+"/{connectorType}")
135   @Produces(MediaType.APPLICATION_JSON)
136   @RolesAllowed("administrators")
137   @ApiOperation(value = "Return the Indexing Connectors with the specified Connector Type")
138   @ApiResponses(value = {
139       @ApiResponse(code = 200, message = "Successful retrieval of the Indexing Connector"),
140       @ApiResponse(code = 404, message = "Indexing Connector with specified type Not Found"),
141       @ApiResponse(code = 500, message = "Can't generate JSON file") })
142   public Response getConnector(
143       @ApiParam(
144           value = "Type of the Indexing Connector to retrieve",
145           required = true)
146       @PathParam("connectorType")
147       String connectorType,
148       @ApiParam(
149           value = "The name of a JavaScript function to be used as the JSONP callback",
150           required = false)
151       @QueryParam("jsonp")
152       String jsonp
153   ) {
154 
155     IndexingServiceConnector connector = indexingOperationProcessor.getConnectors().get(connectorType);
156 
157     if (connector == null) return Response.status(HTTPStatus.NOT_FOUND).build();
158 
159     Response.ResponseBuilder response;
160 
161     //Manage json-callback parameter
162     if (StringUtils.isNotBlank(jsonp)) {
163       try {
164         response = buildJsonCallBack(connector, jsonp);
165       } catch (JsonException e) {
166         LOG.error(e);
167         response = Response.status(HTTPStatus.INTERNAL_ERROR);
168       }
169     }
170     else {
171       response = Response.ok(connector, MediaType.APPLICATION_JSON);
172     }
173 
174     return response.build();
175   }
176 
177   @PUT
178   @Path(IndexingManagementRestServiceV1.CONNECTORS_URI+"/{connectorType}")
179   @Consumes(MediaType.APPLICATION_JSON)
180   @RolesAllowed("administrators")
181   @ApiOperation(value = "Update an Indexing Connector to enable / disable it")
182   @ApiResponses(value = {
183       @ApiResponse(code = 200, message = "Successful update of the Indexing Connector"),
184       @ApiResponse(code = 404, message = "Indexing Connector with specified type Not Found") })
185   public Response updateConnector(
186       @ApiParam(
187           value = "Type of the Indexing Connector to update",
188           required = true)
189       @PathParam("connectorType")
190       String connectorType,
191       @ApiParam(
192           value = "An Indexing Connector Resource",
193           required = true)
194       ConnectorResource connectorResource
195   ) {
196 
197     if (indexingOperationProcessor.getConnectors().get(connectorType) == null) {
198       return Response.status(HTTPStatus.NOT_FOUND).build();
199     }
200 
201     indexingOperationProcessor.getConnectors().get(connectorType).setEnable(connectorResource.isEnable());
202 
203     return Response.ok().build();
204   }
205 
206   // Indexing Operations
207 
208   @GET
209   @Path(IndexingManagementRestServiceV1.OPERATIONS_URI)
210   @Produces(MediaType.APPLICATION_JSON)
211   @RolesAllowed("administrators")
212   @ApiOperation(value = "Return all Indexing Operations")
213   @ApiResponses(value = {
214       @ApiResponse(code = 200, message = "Successful retrieval of all Indexing Operations"),
215       @ApiResponse(code = 500, message = "Can't generate JSON file") })
216   public Response getOperations(
217       @ApiParam(
218           value = "The name of a JavaScript function to be used as the JSONP callback",
219           required = false)
220       @QueryParam("jsonp")
221       String jsonp,
222       @ApiParam(value = "The starting point when paging through a list of entities",
223           required = false)
224       @QueryParam("offset")
225       int offset,
226       @ApiParam(value = "The maximum number of results when paging through a list of entities. " +
227           "If not specified or exceed the *query_limit* configuration of Indexing Management rest service, " +
228           "it will use the *query_limit*",
229           required = false)
230       @QueryParam("limit")
231       int limit,
232       @ApiParam(
233           value = "Tell the service if it must return the size of the collection in the store",
234           required = false)
235       @QueryParam("returnSize")
236       boolean returnSize
237   ) {
238 
239     offset = parseOffset(offset);
240     limit = parseLimit(limit);
241 
242     //Get operations
243     List<IndexingOperation> operations = new ArrayList<>(indexingService.getOperations(offset, limit));
244 
245     CollectionResource<IndexingOperation> operationData;
246     
247     //Manage return size parameter
248     if (returnSize) {
249       int operationNb = indexingService.getNumberOperations().intValue();
250       operationData = new CollectionSizeResource<>(operations, operationNb);
251     }
252     else {
253       operationData = new CollectionResource<>(operations);
254     }
255     operationData.setLimit(limit);
256     operationData.setOffset(offset);
257 
258     Response.ResponseBuilder response;
259 
260     //Manage json-callback parameter
261     if (StringUtils.isNotBlank(jsonp)) {
262       try {
263         response = buildJsonCallBack(operationData, jsonp);
264       } catch (JsonException e) {
265         LOG.error(e);
266         response = Response.status(HTTPStatus.INTERNAL_ERROR);
267       }
268     }
269     else {
270       response = Response.ok(operationData, MediaType.APPLICATION_JSON);
271     }
272     
273     return response.build();
274   }
275 
276   @POST
277   @Path(IndexingManagementRestServiceV1.OPERATIONS_URI)
278   @Consumes(MediaType.APPLICATION_JSON)
279   @RolesAllowed("administrators")
280   @ApiOperation(value = "Add an Indexing Operation to the queue")
281   @ApiResponses(value = {
282       @ApiResponse(code = 201, message = "Indexing Operation successfully added"),
283       @ApiResponse(code = 400, message = "The specified Indexing Operation is unknown")})
284   public Response addOperation(
285       @ApiParam(
286           value = "An Indexing Operation Resource",
287           required = true)
288               OperationResource operationResource
289   ) {
290 
291     switch (operationResource.getOperation()) {
292 
293       case "init": indexingService.init(operationResource.getEntityType());
294         break;
295       case "index": indexingService.index(operationResource.getEntityType(), operationResource.getEntityId());
296         break;
297       case "reindex": indexingService.reindex(operationResource.getEntityType(), operationResource.getEntityId());
298         break;
299       case "unindex": indexingService.unindex(operationResource.getEntityType(), operationResource.getEntityId());
300         break;
301       case "reindexAll": indexingService.reindexAll(operationResource.getEntityType());
302         break;
303       case "unindexAll": indexingService.unindexAll(operationResource.getEntityType());
304         break;
305       default: return getBadRequestResponse().build();
306 
307     }
308 
309     return Response.status(HTTPStatus.CREATED).build();
310   }
311 
312   @DELETE
313   @Path(IndexingManagementRestServiceV1.OPERATIONS_URI)
314   @RolesAllowed("administrators")
315   @ApiOperation(value = "Delete all Indexing Operation")
316   @ApiResponses(value = {
317       @ApiResponse(code = 200, message = "Successful deletion of all Indexing Operations") })
318   public Response deleteOperations() {
319 
320     indexingService.deleteAllOperations();
321 
322     return Response.ok().build();
323 
324   }
325 
326   @GET
327   @Path(IndexingManagementRestServiceV1.OPERATIONS_URI+"/{operationId}")
328   @Produces(MediaType.APPLICATION_JSON)
329   @RolesAllowed("administrators")
330   @ApiOperation(value = "Return the Indexing Operation with the specified Opertion Id")
331   @ApiResponses(value = {
332       @ApiResponse(code = 200, message = "Successful retrieval of the Indexing Operation"),
333       @ApiResponse(code = 404, message = "Indexing Operation with specified Id Not Found"),
334       @ApiResponse(code = 500, message = "Can't generate JSON file") })
335   public Response getOperation(
336       @ApiParam(
337           value = "Id of the Indexing Operation to retrieve",
338           required = true)
339       @PathParam("operationId")
340       String operationId,
341       @ApiParam(
342           value = "The name of a JavaScript function to be used as the JSONP callback",
343           required = false)
344       @QueryParam("jsonp")
345       String jsonp
346   ) {
347 
348     IndexingOperation operation = indexingService.getOperation(operationId);
349 
350     if (operation == null) return Response.status(HTTPStatus.NOT_FOUND).build();
351 
352     Response.ResponseBuilder response;
353 
354     //Manage json-callback parameter
355     if (StringUtils.isNotBlank(jsonp)) {
356       try {
357         response = buildJsonCallBack(operation, jsonp);
358       } catch (JsonException e) {
359         LOG.error(e);
360         response = Response.status(HTTPStatus.INTERNAL_ERROR);
361       }
362     }
363     else {
364       response = Response.ok(operation, MediaType.APPLICATION_JSON);
365     }
366 
367     return response.build();
368 
369   }
370 
371   @DELETE
372   @Path(IndexingManagementRestServiceV1.OPERATIONS_URI+"/{operationId}")
373   @RolesAllowed("administrators")
374   @ApiOperation(value = "Delete a specified Indexing Operation")
375   @ApiResponses(value = {
376       @ApiResponse(code = 200, message = "Successful deletion of the Indexing Operations"),
377       @ApiResponse(code = 404, message = "Indexing Operation with specified Id Not Found") })
378   public Response DeleteOperation(
379       @ApiParam(
380           value = "Id of the Indexing Operation to delete",
381           required = true)
382       @PathParam("operationId")
383       String operationId
384   ) {
385 
386     IndexingOperation operation = indexingService.getOperation(operationId);
387 
388     if (operation == null) return Response.status(HTTPStatus.NOT_FOUND).build();
389 
390     indexingService.deleteOperation(operation);
391 
392     return Response.ok().build();
393 
394   }
395 
396   //TODO manage Bulk Operation
397 
398   // Indexing Errors
399 
400   //TODO implement Error REST Service
401 
402   // Utils method
403 
404   private Response.ResponseBuilder buildJsonCallBack(Serializable resource, String jsonp) throws JsonException {
405     JsonValue value = new JsonGeneratorImpl().createJsonObject(resource);
406     StringBuilder sb = new StringBuilder(jsonp);
407     sb.append("(").append(value).append(");");
408     return Response.ok(sb.toString(), new MediaType("text", "javascript"));
409   }
410 
411   /**
412    * Doesn't allow limit parameter to exceed the default query_limit
413    */
414   private int parseLimit(int limit) {
415     return (limit <=0 || limit > CollectionResource.QUERY_LIMIT) ? CollectionResource.QUERY_LIMIT : limit;
416   }
417 
418   /**
419    * Default offset is 0
420    */
421   private int parseOffset(int offset) {
422     return (offset <=0) ? 0 : offset;
423   }
424 
425   private Response.ResponseBuilder getBadRequestResponse() {
426     Calendar today = Calendar.getInstance();
427     if (today.get(Calendar.DAY_OF_MONTH) == 1 && today.get(Calendar.MONTH) == Calendar.APRIL) {
428       return Response.status(418);
429     }
430     return Response.status(HTTPStatus.BAD_REQUEST);
431   }
432 
433 }
434