IndexEndpoint.java
/*
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.elasticsearch.index.endpoint;
import org.opencastproject.elasticsearch.index.ElasticsearchIndex;
import org.opencastproject.elasticsearch.index.rebuild.IndexProducer;
import org.opencastproject.elasticsearch.index.rebuild.IndexRebuildService;
import org.opencastproject.elasticsearch.index.rebuild.IndexRebuildService.DataType;
import org.opencastproject.elasticsearch.index.rebuild.IndexRebuildService.Service;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.security.util.SecurityContext;
import org.opencastproject.util.RestUtil.R;
import org.opencastproject.util.doc.rest.RestParameter;
import org.opencastproject.util.doc.rest.RestQuery;
import org.opencastproject.util.doc.rest.RestResponse;
import org.opencastproject.util.doc.rest.RestService;
import org.apache.commons.lang3.EnumUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* The index endpoint allows the management of the elasticsearch index.
*/
@Path("/index")
@RestService(name = "IndexEndpoint", title = "Index Endpoint",
abstractText = "Provides operations related to the index that serves both the Admin UI and the External API",
notes = {})
@Component(
immediate = true,
property = {
"service.description=Index Endpoint",
"opencast.service.type=org.opencastproject.elasticsearch.index.endpoint",
"opencast.service.path=/index"
},
service = { IndexEndpoint.class }
)
@JaxrsResource
public class IndexEndpoint {
/** The logging facility */
private static final Logger logger = LoggerFactory.getLogger(IndexEndpoint.class);
/** The executor service */
private final ExecutorService executor = Executors.newSingleThreadExecutor();
/** The index */
private ElasticsearchIndex elasticsearchIndex;
/** The security service */
protected SecurityService securityService = null;
/** The index rebuild service **/
private IndexRebuildService indexRebuildService = null;
@Reference
public void setElasticsearchIndex(ElasticsearchIndex elasticsearchIndex) {
this.elasticsearchIndex = elasticsearchIndex;
}
@Reference
public void setIndexRebuildService(IndexRebuildService indexRebuildService) {
this.indexRebuildService = indexRebuildService;
}
@Reference
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
@Activate
public void activate() {
logger.info("Activate IndexEndpoint");
}
@POST
@Path("clear")
@RestQuery(name = "clearIndex", description = "Clear the index",
returnDescription = "OK if index is cleared", responses = {
@RestResponse(description = "Index is cleared", responseCode = HttpServletResponse.SC_OK),
@RestResponse(description = "Unable to clear index",
responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) })
public Response clearIndex() {
final SecurityContext securityContext = new SecurityContext(securityService, securityService.getOrganization(),
securityService.getUser());
return securityContext.runInContext(() -> {
try {
logger.info("Clear the index");
elasticsearchIndex.clear();
return R.ok();
} catch (Throwable t) {
logger.error("Clearing the index failed", t);
return R.serverError();
}
});
}
@POST
@Path("rebuild/{service}")
@RestQuery(name = "partiallyRebuildIndex",
description = "Repopulates the Index from an specific service",
returnDescription = "OK if repopulation has started", pathParameters = {
@RestParameter(name = "service", isRequired = true, description = "The service to recreate index from. "
+ "The available services are: Series, Scheduler, AssetManager, Comments, Workflow and Search. "
+ "The service order (see above) is very important except for Search! Make sure, you do not run index rebuild"
+ "for more than one service at a time!",
type = RestParameter.Type.STRING) }, responses = {
@RestResponse(description = "OK if repopulation has started", responseCode = HttpServletResponse.SC_OK) })
public Response partiallyRebuildIndex(@PathParam("service") final String serviceStr) {
Service service = EnumUtils.getEnum(Service.class, serviceStr);
if (service == null) {
return R.badRequest("The given path param for service was invalid.");
}
IndexProducer indexProducer = indexRebuildService.getIndexProducer(service);
final SecurityContext securityContext = new SecurityContext(securityService, securityService.getOrganization(),
securityService.getUser());
executor.execute(() -> securityContext.runInContext(() -> {
try {
logger.info("Starting to repopulate the index from service {}", service);
indexRebuildService.rebuildIndex(indexProducer, DataType.ALL);
} catch (Throwable t) {
logger.error("Repopulating the index failed", t);
}
}));
return R.ok();
}
@POST
@Path("rebuild/{service}/{type}")
@RestQuery(name = "partiallyRebuildIndexByPart",
description = "Repopulates the index for a specific service, but only for a specific type of data "
+ "in order to save time. "
+ "Only use this endpoint if you have been explicitly told to or know what you are doing, else you risk "
+ "inconsistent data in the index!",
returnDescription = "OK if repopulation has started",
pathParameters = {
@RestParameter(
name = "service",
isRequired = true,
description = "The service to recreate index from. The available services are: AssetManager. ",
type = RestParameter.Type.STRING
),
@RestParameter(
name = "type",
isRequired = true,
description = "The type of data to re-index. The available types are: ACL. ",
type = RestParameter.Type.STRING
)
},
responses = {
@RestResponse(
description = "OK if repopulation has started",
responseCode = HttpServletResponse.SC_OK
),
@RestResponse(
description = "If the given parameters aren't one of the supported values",
responseCode = HttpServletResponse.SC_BAD_REQUEST
)
}
)
public Response partiallyRebuildIndexByType(
@PathParam("service") final String serviceStr,
@PathParam("type") final String dataTypeStr
) {
Service service = EnumUtils.getEnum(Service.class, serviceStr);
if (service == null) {
return R.badRequest("The given path param for service was invalid.");
}
IndexProducer indexProducer = indexRebuildService.getIndexProducer(service);
DataType dataType = EnumUtils.getEnum(DataType.class, dataTypeStr);
if (dataType == null || !indexProducer.dataTypeSupported(dataType)) {
return R.badRequest("The given path param for data type was invalid.");
}
final SecurityContext securityContext = new SecurityContext(securityService, securityService.getOrganization(),
securityService.getUser());
executor.execute(() -> securityContext.runInContext(() -> {
try {
logger.info("Starting to repopulate the index from service {}", service);
indexRebuildService.rebuildIndex(indexProducer, dataType);
} catch (Throwable t) {
logger.error("Repopulating the index failed", t);
}
}));
return R.ok();
}
@POST
@Path("rebuild")
@RestQuery(name = "rebuild", description = "Clear and repopulates the Index directly from the "
+ "Services",
returnDescription = "OK if repopulation has started", responses = {
@RestResponse(description = "OK if repopulation has started", responseCode = HttpServletResponse.SC_OK) })
public Response rebuildIndex() {
final SecurityContext securityContext = new SecurityContext(securityService, securityService.getOrganization(),
securityService.getUser());
executor.execute(() -> securityContext.runInContext(() -> {
try {
logger.info("Starting to repopulate the index");
indexRebuildService.rebuildIndex(elasticsearchIndex);
} catch (Throwable t) {
logger.error("Repopulating the index failed", t);
}
}));
return R.ok();
}
@POST
@Path("resume/{service}")
@RestQuery(name = "resumeIndexRebuild",
description = "Starts repopulating the Index from an specific service and will then continue with the rest "
+ "of the services that come afterwards",
returnDescription = "OK if repopulation has started", pathParameters = {
@RestParameter(name = "service", isRequired = true, description = "The service to start recreating the index "
+ "from. "
+ "The available services are: Series, Scheduler, AssetManager, Comments and Workflow. "
+ "All services that come after the specified service in the order above will also run.",
type = RestParameter.Type.STRING) }, responses = {
@RestResponse(description = "OK if repopulation has started", responseCode = HttpServletResponse.SC_OK) })
public Response resumeIndexRebuild(@PathParam("service") final String serviceStr) {
Service service = EnumUtils.getEnum(Service.class, serviceStr);
if (service == null) {
return R.badRequest("The given path param for service was invalid.");
}
final SecurityContext securityContext = new SecurityContext(securityService, securityService.getOrganization(),
securityService.getUser());
executor.execute(() -> securityContext.runInContext(() -> {
try {
logger.info("Resume repopulating the index from service {}", service);
indexRebuildService.resumeIndexRebuild(service);
} catch (Throwable t) {
logger.error("Repopulating the index failed", t);
}
}));
return R.ok();
}
@GET
@Path("rebuild/states.json")
@Produces(MediaType.APPLICATION_JSON)
@RestQuery(name = "getrebuildstates", description = "Returns the index rebuild service"
+ "repopulation states", returnDescription = "The repopulation states of the index rebuild services",
responses = {
@RestResponse(description = "Returns the repopulation states of the index rebuild services",
responseCode = HttpServletResponse.SC_OK),
})
public Response getRebuildStates() {
Map<String, String> states = indexRebuildService.getRebuildStates();
JSONArray statesAsJson = new JSONArray();
for (Map.Entry<String, String> entry : states.entrySet()) {
JSONObject data = new JSONObject();
data.put("type", entry.getKey());
data.put("state", entry.getValue());
data.put("executionOrder", IndexRebuildService.Service.valueOf(entry.getKey()).ordinal());
statesAsJson.add(data);
}
JSONObject service = new JSONObject();
service.put("service", statesAsJson);
JSONObject services = new JSONObject();
services.put("services", service);
return Response.ok(services.toJSONString()).build();
}
}