/*
* JBoss, Home of Professional Open Source
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
*/
package org.searchisko.api.rest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
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;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.get.GetResponse;
import org.searchisko.api.ContentObjectFields;
import org.searchisko.api.rest.exception.NotAuthorizedException;
import org.searchisko.api.rest.exception.RequiredFieldException;
import org.searchisko.api.security.Role;
import org.searchisko.api.service.AuthenticationUtilService;
import org.searchisko.api.service.CustomTagService;
import org.searchisko.api.service.ProviderService;
import org.searchisko.api.service.ProviderService.ProviderContentTypeInfo;
import org.searchisko.api.service.SearchClientService;
import org.searchisko.api.service.SearchIndexMissingException;
import org.searchisko.api.util.SearchUtils;
import org.searchisko.persistence.jpa.model.Tag;
import org.searchisko.persistence.service.CustomTagPersistenceService;
/**
* REST API endpoint for 'Custom Tag API'.
*
* @author Jiri Mauritz (jirmauritz at gmail dot com)
* @author Vlastimil Elias (velias at redhat dot com)
*/
@RequestScoped
@Path("/tagging")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class CustomTagRestService extends RestServiceBase {
public static final String QUERY_PARAM_ID = "id";
public static final String DATA_FIELD_TAGGING = "tag";
@Inject
protected Logger log;
@Inject
protected ProviderService providerService;
@Inject
protected SearchClientService searchClientService;
@Inject
protected CustomTagService customTagService;
@Inject
protected CustomTagPersistenceService customTagPersistenceService;
@Inject
protected AuthenticationUtilService authenticationUtilService;
/**
* Custom authentication check if user is in role {@link org.searchisko.api.security.Role#TAGS_MANAGER} or
* {@link org.searchisko.api.security.Role#ADMIN} or <code>tags_manager_contentType</code>
*
* @param contentType - check permission for content type
*
* @throws NotAuthorizedException if user doesn't have required role
*/
protected void checkIfUserAuthenticated(String contentType) throws NotAuthorizedException {
if (!(authenticationUtilService.isUserInAnyOfRoles(true, Role.TAGS_MANAGER, Role.TAGS_MANAGER + "_" + contentType))) {
throw new NotAuthorizedException("User Not Authorized for Tagging API");
}
}
@GET
@Path("/{" + QUERY_PARAM_ID + "}")
@Produces(MediaType.APPLICATION_JSON)
public Object getTagsByContent(@PathParam(QUERY_PARAM_ID) String contentSysId) {
contentSysId = SearchUtils.trimToNull(contentSysId);
// validation
if (contentSysId == null) {
throw new RequiredFieldException(QUERY_PARAM_ID);
}
// check if tagged document exists
String type = null;
try {
type = providerService.parseTypeNameFromSysId(contentSysId);
} catch (IllegalArgumentException e) {
log.fine("bad format or unknown type for content sys_id=" + contentSysId);
return Response.status(Response.Status.BAD_REQUEST).entity(QUERY_PARAM_ID + " format is invalid").build();
}
ProviderContentTypeInfo typeInfo = providerService.findContentType(type);
if (typeInfo == null) {
log.fine("unknown type for content with sys_id=" + contentSysId);
return Response.status(Response.Status.NOT_FOUND).entity("content type is unknown").build();
}
checkIfUserAuthenticated(providerService.parseTypeNameFromSysId(contentSysId));
List<Tag> tagList = customTagPersistenceService.getTagsByContent(contentSysId);
if (tagList != null && !tagList.isEmpty()) {
Map<String, Object> result = tagsToJSON(tagList);
return result;
} else {
return Response.status(Status.NOT_FOUND).build();
}
}
/**
* Convert {@link Tag} object into JSON map.
*
* @param tags list of tags to convert
* @return JSON map with information about tag
*/
protected Map<String, Object> tagsToJSON(List<Tag> tags) {
Map<String, Object> result = new HashMap<>();
// use tree set to remove duplicities and order results
Set<String> labels = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
for (Tag tag : tags) {
labels.add(tag.getTagLabel());
}
result.put(DATA_FIELD_TAGGING, new ArrayList<>(labels));
return result;
}
@GET
@Path("/type/{" + QUERY_PARAM_ID + "}")
@Produces(MediaType.APPLICATION_JSON)
public Object getTagsByContentType(@PathParam(QUERY_PARAM_ID) String contentType) {
contentType = SearchUtils.trimToNull(contentType);
// validation
if (contentType == null) {
throw new RequiredFieldException(QUERY_PARAM_ID);
}
ProviderContentTypeInfo typeInfo = providerService.findContentType(contentType);
if (typeInfo == null) {
log.fine("unknown type id=" + contentType);
return Response.status(Response.Status.NOT_FOUND).entity("content type is unknown").build();
}
checkIfUserAuthenticated(contentType);
List<Tag> tagList = customTagPersistenceService.getTagsByContentType(contentType);
if (tagList != null && !tagList.isEmpty()) {
Map<String, Object> result = tagsToJSON(tagList);
return result;
} else {
return Response.status(Status.NOT_FOUND).build();
}
}
@SuppressWarnings("unchecked")
@POST
@Path("/{" + QUERY_PARAM_ID + "}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Object postTag(@PathParam(QUERY_PARAM_ID) String contentSysId, Map<String, Object> requestContent) {
contentSysId = SearchUtils.trimToNull(contentSysId);
// validation
if ((contentSysId == null) || (requestContent == null)) {
throw new RequiredFieldException(QUERY_PARAM_ID);
}
// check if tagged document exists
String type = null;
try {
type = providerService.parseTypeNameFromSysId(contentSysId);
} catch (IllegalArgumentException e) {
log.fine("bad format or unknown type for content sys_id=" + contentSysId);
return Response.status(Response.Status.BAD_REQUEST).entity(QUERY_PARAM_ID + " format is invalid").build();
}
ProviderContentTypeInfo typeInfo = providerService.findContentType(type);
if (typeInfo == null) {
log.fine("unknown type for content with sys_id=" + contentSysId);
return Response.status(Response.Status.NOT_FOUND).entity("content type is unknown").build();
}
checkIfUserAuthenticated(providerService.parseTypeNameFromSysId(contentSysId));
String currentContributorId = authenticationUtilService.getAuthenticatedContributor(true);
String tag = null;
if (requestContent.get(DATA_FIELD_TAGGING) == null) {
throw new RequiredFieldException(DATA_FIELD_TAGGING);
}
if (!(requestContent.get(DATA_FIELD_TAGGING) instanceof String)) {
return Response.status(Status.BAD_REQUEST).entity(DATA_FIELD_TAGGING + " field must be text string").build();
}
tag = StringUtils.trim((String) requestContent.get(DATA_FIELD_TAGGING));
if (tag.isEmpty()) {
return Response.status(Status.BAD_REQUEST).entity(DATA_FIELD_TAGGING + " field cannot be empty").build();
}
try {
GetResponse getResponse = getContentDocument(typeInfo, contentSysId);
if (!getResponse.isExists()) {
return Response.status(Response.Status.NOT_FOUND).build();
}
// check for same tag in provider tags
Map<String, Object> source = getResponse.getSource();
SortedSet<String> providerTags = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
List<String> providersList = (List<String>) source.get(ContentObjectFields.TAGS);
if (providersList != null) {
providerTags.addAll(providersList);
}
boolean created;
if (!(providerTags.contains(tag))) {
// store tag
Tag tagObject = new Tag();
tagObject.setContentId(contentSysId);
tagObject.setContributorId(currentContributorId);
tagObject.setTagLabel(tag);
created = customTagPersistenceService.createTag(tagObject);
if (created) {
customTagService.updateSysTagsField(source);
searchClientService.performPut(getResponse.getIndex(), getResponse.getType(), contentSysId, source);
}
} else
created = false;
return Response.status(created ? Response.Status.CREATED : Response.Status.OK).build();
} catch (SearchIndexMissingException e) {
return Response.status(Response.Status.NOT_FOUND).build();
}
}
@DELETE
@Path("/{" + QUERY_PARAM_ID + "}/_all")
public Object deleteTagsForContent(@PathParam(QUERY_PARAM_ID) String contentSysId) {
contentSysId = SearchUtils.trimToNull(contentSysId);
// validation
if (contentSysId == null) {
throw new RequiredFieldException(QUERY_PARAM_ID);
}
// check if tagged document exists
String type = null;
try {
type = providerService.parseTypeNameFromSysId(contentSysId);
} catch (IllegalArgumentException e) {
log.fine("bad format or unknown type for content sys_id=" + contentSysId);
return Response.status(Response.Status.BAD_REQUEST).entity(QUERY_PARAM_ID + " format is invalid").build();
}
ProviderContentTypeInfo typeInfo = providerService.findContentType(type);
if (typeInfo == null) {
log.fine("unknown type for content with sys_id=" + contentSysId);
return Response.status(Response.Status.NOT_FOUND).entity("content type is unknown").build();
}
checkIfUserAuthenticated(providerService.parseTypeNameFromSysId(contentSysId));
// delete tags from custom tags
customTagPersistenceService.deleteTagsForContent(contentSysId);
// delete tags from SYS_TAG field (update SYS_TAG field)
GetResponse getResponse;
try {
getResponse = getContentDocument(typeInfo, contentSysId);
} catch (SearchIndexMissingException ex) {
return Response.status(Response.Status.NOT_FOUND).build();
}
if (!getResponse.isExists()) {
return Response.status(Response.Status.NOT_FOUND).build();
}
Map<String, Object> source = getResponse.getSource();
customTagService.updateSysTagsField(source);
searchClientService.performPut(getResponse.getIndex(), getResponse.getType(), contentSysId, source);
return Response.status(Status.OK).build();
}
@DELETE
@Path("/{" + QUERY_PARAM_ID + "}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Object deleteTag(@PathParam(QUERY_PARAM_ID) String contentSysId, Map<String, Object> requestContent) {
contentSysId = SearchUtils.trimToNull(contentSysId);
// validation
if ((contentSysId == null) || (requestContent == null)) {
throw new RequiredFieldException(QUERY_PARAM_ID);
}
// check if tagged document exists
String type = null;
try {
type = providerService.parseTypeNameFromSysId(contentSysId);
} catch (IllegalArgumentException e) {
log.fine("bad format or unknown type for content sys_id=" + contentSysId);
return Response.status(Response.Status.BAD_REQUEST).entity(QUERY_PARAM_ID + " format is invalid").build();
}
ProviderContentTypeInfo typeInfo = providerService.findContentType(type);
if (typeInfo == null) {
log.fine("unknown type for content with sys_id=" + contentSysId);
return Response.status(Response.Status.NOT_FOUND).entity("content type is unknown").build();
}
checkIfUserAuthenticated(providerService.parseTypeNameFromSysId(contentSysId));
String tagLabel = null;
try {
tagLabel = StringUtils.trim((String) requestContent.get(DATA_FIELD_TAGGING));
} catch (ClassCastException e) {
return Response.status(Status.BAD_REQUEST).entity(DATA_FIELD_TAGGING + " field must be text string").build();
}
if (tagLabel == null) {
throw new RequiredFieldException(DATA_FIELD_TAGGING);
}
if (tagLabel.isEmpty()) {
return Response.status(Status.BAD_REQUEST).entity(DATA_FIELD_TAGGING + " field cannot be empty").build();
}
// delete tag from custom tags
customTagPersistenceService.deleteTag(contentSysId, tagLabel);
// delete tag from SYS_TAG field (update SYS_TAG field)
GetResponse getResponse;
try {
getResponse = getContentDocument(typeInfo, contentSysId);
} catch (SearchIndexMissingException ex) {
return Response.status(Response.Status.NOT_FOUND).build();
}
if (!getResponse.isExists()) {
return Response.status(Response.Status.NOT_FOUND).build();
}
Map<String, Object> source = getResponse.getSource();
customTagService.updateSysTagsField(source);
searchClientService.performPut(getResponse.getIndex(), getResponse.getType(), contentSysId, source);
return Response.status(Status.OK).build();
}
private GetResponse getContentDocument(ProviderContentTypeInfo typeInfo, String contentSysId)
throws SearchIndexMissingException {
String indexName = ProviderService.extractIndexName(typeInfo, typeInfo.getTypeName());
String indexType = ProviderService.extractIndexType(typeInfo, typeInfo.getTypeName());
return searchClientService.performGet(indexName, indexType, contentSysId);
}
}