IndexingManagementRestServiceV1.java
/*
* Copyright (C) 2003-2015 eXo Platform SAS.
*
* This program 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.
*
* This program 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 this program. If not, see http://www.gnu.org/licenses/ .
*/
package org.exoplatform.commons.search.rest;
import io.swagger.annotations.*;
import org.apache.commons.lang.StringUtils;
import org.exoplatform.commons.search.domain.IndexingOperation;
import org.exoplatform.commons.search.index.IndexingOperationProcessor;
import org.exoplatform.commons.search.index.IndexingService;
import org.exoplatform.commons.search.index.IndexingServiceConnector;
import org.exoplatform.commons.search.index.impl.QueueIndexingService;
import org.exoplatform.commons.search.rest.resource.CollectionResource;
import org.exoplatform.commons.search.rest.resource.CollectionSizeResource;
import org.exoplatform.commons.search.rest.resource.ConnectorResource;
import org.exoplatform.commons.search.rest.resource.OperationResource;
import org.exoplatform.common.http.HTTPStatus;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.rest.resource.ResourceContainer;
import org.exoplatform.ws.frameworks.json.impl.JsonException;
import org.exoplatform.ws.frameworks.json.impl.JsonGeneratorImpl;
import org.exoplatform.ws.frameworks.json.value.JsonValue;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
/**
* Created by The eXo Platform SAS
* Author : Thibault Clement
* tclement@exoplatform.com
* 10/6/15
*/
@Path(IndexingManagementRestServiceV1.BASE_VERSION_URI+ IndexingManagementRestServiceV1.INDEXING_MANAGEMENT_URI)
@RolesAllowed("administrators")
@Api(
value = IndexingManagementRestServiceV1.BASE_VERSION_URI+ IndexingManagementRestServiceV1.INDEXING_MANAGEMENT_URI,
description = "Entry point for Indexing Management resources",
basePath = IndexingManagementRestServiceV1.BASE_VERSION_URI+ IndexingManagementRestServiceV1.INDEXING_MANAGEMENT_URI
)
public class IndexingManagementRestServiceV1 implements ResourceContainer {
public final static String BASE_VERSION_URI = "/v1";
public final static String INDEXING_MANAGEMENT_URI = "/indexingManagement";
public final static String CONNECTORS_URI = "/connectors";
public final static String OPERATIONS_URI = "/operations";
public final static String ERRORS_URI = "/errors";
private final static Log LOG = ExoLogger.getLogger(IndexingManagementRestServiceV1.class);
private QueueIndexingService indexingService;
private IndexingOperationProcessor indexingOperationProcessor;
public IndexingManagementRestServiceV1(IndexingService indexingService, IndexingOperationProcessor indexingOperationProcessor) {
this.indexingService = (QueueIndexingService) indexingService;
this.indexingOperationProcessor = indexingOperationProcessor;
}
// Indexing Service Connectors
@GET
@Path(IndexingManagementRestServiceV1.CONNECTORS_URI)
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed("administrators")
@ApiOperation(value = "Return all Indexing Connectors")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of all Indexing Connectors"),
@ApiResponse(code = 500, message = "Can't generate JSON file") })
public Response getConnectors(
@ApiParam(
value = "The name of a JavaScript function to be used as the JSONP callback",
required = false)
@QueryParam("jsonp")
String jsonp,
@ApiParam(
value = "Tell the service if it must return the size of the collection in the store",
required = false)
@QueryParam("returnSize")
boolean returnSize
) {
//Get connectors
List<IndexingServiceConnector> connectors = new ArrayList<>(indexingOperationProcessor.getConnectors().values());
CollectionResource<IndexingServiceConnector> connectorData;
//Manage return size parameter
if (returnSize) {
int connectorNb = indexingOperationProcessor.getConnectors().size();
connectorData = new CollectionSizeResource<>(connectors, connectorNb);
}
else {
connectorData = new CollectionResource<>(connectors);
}
Response.ResponseBuilder response;
//Manage json-callback parameter
if (StringUtils.isNotBlank(jsonp)) {
try {
response = buildJsonCallBack(connectorData, jsonp);
} catch (JsonException e) {
LOG.error(e);
response = Response.status(HTTPStatus.INTERNAL_ERROR);
}
}
else {
response = Response.ok(connectorData, MediaType.APPLICATION_JSON);
}
return response.build();
}
@GET
@Path(IndexingManagementRestServiceV1.CONNECTORS_URI+"/{connectorType}")
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed("administrators")
@ApiOperation(value = "Return the Indexing Connectors with the specified Connector Type")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of the Indexing Connector"),
@ApiResponse(code = 404, message = "Indexing Connector with specified type Not Found"),
@ApiResponse(code = 500, message = "Can't generate JSON file") })
public Response getConnector(
@ApiParam(
value = "Type of the Indexing Connector to retrieve",
required = true)
@PathParam("connectorType")
String connectorType,
@ApiParam(
value = "The name of a JavaScript function to be used as the JSONP callback",
required = false)
@QueryParam("jsonp")
String jsonp
) {
IndexingServiceConnector connector = indexingOperationProcessor.getConnectors().get(connectorType);
if (connector == null) return Response.status(HTTPStatus.NOT_FOUND).build();
Response.ResponseBuilder response;
//Manage json-callback parameter
if (StringUtils.isNotBlank(jsonp)) {
try {
response = buildJsonCallBack(connector, jsonp);
} catch (JsonException e) {
LOG.error(e);
response = Response.status(HTTPStatus.INTERNAL_ERROR);
}
}
else {
response = Response.ok(connector, MediaType.APPLICATION_JSON);
}
return response.build();
}
@PUT
@Path(IndexingManagementRestServiceV1.CONNECTORS_URI+"/{connectorType}")
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed("administrators")
@ApiOperation(value = "Update an Indexing Connector to enable / disable it")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful update of the Indexing Connector"),
@ApiResponse(code = 404, message = "Indexing Connector with specified type Not Found") })
public Response updateConnector(
@ApiParam(
value = "Type of the Indexing Connector to update",
required = true)
@PathParam("connectorType")
String connectorType,
@ApiParam(
value = "An Indexing Connector Resource",
required = true)
ConnectorResource connectorResource
) {
if (indexingOperationProcessor.getConnectors().get(connectorType) == null) {
return Response.status(HTTPStatus.NOT_FOUND).build();
}
indexingOperationProcessor.getConnectors().get(connectorType).setEnable(connectorResource.isEnable());
return Response.ok().build();
}
// Indexing Operations
@GET
@Path(IndexingManagementRestServiceV1.OPERATIONS_URI)
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed("administrators")
@ApiOperation(value = "Return all Indexing Operations")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of all Indexing Operations"),
@ApiResponse(code = 500, message = "Can't generate JSON file") })
public Response getOperations(
@ApiParam(
value = "The name of a JavaScript function to be used as the JSONP callback",
required = false)
@QueryParam("jsonp")
String jsonp,
@ApiParam(value = "The starting point when paging through a list of entities",
required = false)
@QueryParam("offset")
int offset,
@ApiParam(value = "The maximum number of results when paging through a list of entities. " +
"If not specified or exceed the *query_limit* configuration of Indexing Management rest service, " +
"it will use the *query_limit*",
required = false)
@QueryParam("limit")
int limit,
@ApiParam(
value = "Tell the service if it must return the size of the collection in the store",
required = false)
@QueryParam("returnSize")
boolean returnSize
) {
offset = parseOffset(offset);
limit = parseLimit(limit);
//Get operations
List<IndexingOperation> operations = new ArrayList<>(indexingService.getOperations(offset, limit));
CollectionResource<IndexingOperation> operationData;
//Manage return size parameter
if (returnSize) {
int operationNb = indexingService.getNumberOperations().intValue();
operationData = new CollectionSizeResource<>(operations, operationNb);
}
else {
operationData = new CollectionResource<>(operations);
}
operationData.setLimit(limit);
operationData.setOffset(offset);
Response.ResponseBuilder response;
//Manage json-callback parameter
if (StringUtils.isNotBlank(jsonp)) {
try {
response = buildJsonCallBack(operationData, jsonp);
} catch (JsonException e) {
LOG.error(e);
response = Response.status(HTTPStatus.INTERNAL_ERROR);
}
}
else {
response = Response.ok(operationData, MediaType.APPLICATION_JSON);
}
return response.build();
}
@POST
@Path(IndexingManagementRestServiceV1.OPERATIONS_URI)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed("administrators")
@ApiOperation(value = "Add an Indexing Operation to the queue")
@ApiResponses(value = {
@ApiResponse(code = 201, message = "Indexing Operation successfully added"),
@ApiResponse(code = 400, message = "The specified Indexing Operation is unknown")})
public Response addOperation(
@ApiParam(
value = "An Indexing Operation Resource",
required = true)
OperationResource operationResource
) {
switch (operationResource.getOperation()) {
case "init": indexingService.init(operationResource.getEntityType());
break;
case "index": indexingService.index(operationResource.getEntityType(), operationResource.getEntityId());
break;
case "reindex": indexingService.reindex(operationResource.getEntityType(), operationResource.getEntityId());
break;
case "unindex": indexingService.unindex(operationResource.getEntityType(), operationResource.getEntityId());
break;
case "reindexAll": indexingService.reindexAll(operationResource.getEntityType());
break;
case "unindexAll": indexingService.unindexAll(operationResource.getEntityType());
break;
default: return getBadRequestResponse().build();
}
return Response.status(HTTPStatus.CREATED).build();
}
@DELETE
@Path(IndexingManagementRestServiceV1.OPERATIONS_URI)
@RolesAllowed("administrators")
@ApiOperation(value = "Delete all Indexing Operation")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful deletion of all Indexing Operations") })
public Response deleteOperations() {
indexingService.deleteAllOperations();
return Response.ok().build();
}
@GET
@Path(IndexingManagementRestServiceV1.OPERATIONS_URI+"/{operationId}")
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed("administrators")
@ApiOperation(value = "Return the Indexing Operation with the specified Opertion Id")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of the Indexing Operation"),
@ApiResponse(code = 404, message = "Indexing Operation with specified Id Not Found"),
@ApiResponse(code = 500, message = "Can't generate JSON file") })
public Response getOperation(
@ApiParam(
value = "Id of the Indexing Operation to retrieve",
required = true)
@PathParam("operationId")
String operationId,
@ApiParam(
value = "The name of a JavaScript function to be used as the JSONP callback",
required = false)
@QueryParam("jsonp")
String jsonp
) {
IndexingOperation operation = indexingService.getOperation(operationId);
if (operation == null) return Response.status(HTTPStatus.NOT_FOUND).build();
Response.ResponseBuilder response;
//Manage json-callback parameter
if (StringUtils.isNotBlank(jsonp)) {
try {
response = buildJsonCallBack(operation, jsonp);
} catch (JsonException e) {
LOG.error(e);
response = Response.status(HTTPStatus.INTERNAL_ERROR);
}
}
else {
response = Response.ok(operation, MediaType.APPLICATION_JSON);
}
return response.build();
}
@DELETE
@Path(IndexingManagementRestServiceV1.OPERATIONS_URI+"/{operationId}")
@RolesAllowed("administrators")
@ApiOperation(value = "Delete a specified Indexing Operation")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful deletion of the Indexing Operations"),
@ApiResponse(code = 404, message = "Indexing Operation with specified Id Not Found") })
public Response DeleteOperation(
@ApiParam(
value = "Id of the Indexing Operation to delete",
required = true)
@PathParam("operationId")
String operationId
) {
IndexingOperation operation = indexingService.getOperation(operationId);
if (operation == null) return Response.status(HTTPStatus.NOT_FOUND).build();
indexingService.deleteOperation(operation);
return Response.ok().build();
}
//TODO manage Bulk Operation
// Indexing Errors
//TODO implement Error REST Service
// Utils method
private Response.ResponseBuilder buildJsonCallBack(Serializable resource, String jsonp) throws JsonException {
JsonValue value = new JsonGeneratorImpl().createJsonObject(resource);
StringBuilder sb = new StringBuilder(jsonp);
sb.append("(").append(value).append(");");
return Response.ok(sb.toString(), new MediaType("text", "javascript"));
}
/**
* Doesn't allow limit parameter to exceed the default query_limit
*/
private int parseLimit(int limit) {
return (limit <=0 || limit > CollectionResource.QUERY_LIMIT) ? CollectionResource.QUERY_LIMIT : limit;
}
/**
* Default offset is 0
*/
private int parseOffset(int offset) {
return (offset <=0) ? 0 : offset;
}
private Response.ResponseBuilder getBadRequestResponse() {
Calendar today = Calendar.getInstance();
if (today.get(Calendar.DAY_OF_MONTH) == 1 && today.get(Calendar.MONTH) == Calendar.APRIL) {
return Response.status(418);
}
return Response.status(HTTPStatus.BAD_REQUEST);
}
}