eu.dasish.annotation.backend.rest.AnnotationResource.java Source code

Java tutorial

Introduction

Here is the source code for eu.dasish.annotation.backend.rest.AnnotationResource.java

Source

/*
 * Copyright (C) 2013 DASISH
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package eu.dasish.annotation.backend.rest;

import eu.dasish.annotation.backend.BackendConstants;
import eu.dasish.annotation.backend.ForbiddenException;
import eu.dasish.annotation.backend.MatchMode;
import eu.dasish.annotation.backend.NotInDataBaseException;
import eu.dasish.annotation.backend.Resource;
import eu.dasish.annotation.backend.dao.ILambda;
import eu.dasish.annotation.schema.Annotation;
import eu.dasish.annotation.schema.AnnotationBody;
import eu.dasish.annotation.schema.AnnotationInfoList;
import eu.dasish.annotation.schema.ObjectFactory;
import eu.dasish.annotation.schema.Access;
import eu.dasish.annotation.schema.AnnotationInfo;
import eu.dasish.annotation.schema.PermissionList;
import eu.dasish.annotation.schema.ReferenceList;
import eu.dasish.annotation.schema.ResponseBody;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Providers;
import javax.xml.bind.JAXBElement;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * A REST class for GETting, POSTing, PUTting and DELETing annotations or their substructures (child elements).
 * Every REST method in the case of successful completion produces the object of the declared output type
 * (a JAXB-element or a message string) or sends a HTTP-error with the corresponding diagnostics otherwise.
 * @author olhsha
 */
@Component
@Path("/annotations")
@Transactional(rollbackFor = { Exception.class })
public class AnnotationResource extends ResourceResource {

    final private String defaultMatchMode = "exact";
    final private String[] admissibleMatchModes = { "exact", "starts_with", "ends_with", "contains" };

    /**
     * 
     * @param httpServletRequest a {@link HttpServletRequest} object; set explicitely in the unit tests.
     */
    public void setHttpServletRequest(HttpServletRequest httpServletRequest) {
        this.httpServletRequest = httpServletRequest;
    }

    /**
     * 
     * @param providers a {@link Providers} object, used by JAXB, also 
     * to validate input and output xml files w.r.t. the dasish schema.
     */
    public void setProviders(Providers providers) {
        this.providers = providers;
    }

    public AnnotationResource() {
    }

    /**
     * 
     * @param externalIdentifier the UUID of an annotation.
     * @return the xml-element representing the annotation with "externalIdentifier" built up 
     * from the "annotation" table and the corresponding junction tables. 
     * @throws IOException if sending an error fails.
     */
    @GET
    @Produces(MediaType.TEXT_XML)
    @Path("{annotationid: " + BackendConstants.regExpIdentifier + "}")
    @Transactional(readOnly = true)
    public JAXBElement<Annotation> getAnnotation(@PathParam("annotationid") String externalIdentifier)
            throws IOException {
        Map params = new HashMap();
        try {
            Annotation result = (Annotation) (new RequestWrappers(this)).wrapRequestResource(params,
                    new GetAnnotation(), Resource.ANNOTATION, Access.READ, externalIdentifier);
            if (result != null) {
                return (new ObjectFactory()).createAnnotation(result);
            } else {
                return (new ObjectFactory()).createAnnotation(new Annotation());
            }
        } catch (NotInDataBaseException e1) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, e1.getMessage());
            return (new ObjectFactory()).createAnnotation(new Annotation());
        } catch (ForbiddenException e2) {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e2.getMessage());
            return (new ObjectFactory()).createAnnotation(new Annotation());
        }
    }

    private class GetAnnotation implements ILambda<Map, Annotation> {

        @Override
        public Annotation apply(Map params) throws NotInDataBaseException {
            return dbDispatcher.getAnnotation((Number) params.get("internalID"));
        }
    }

    /**
     * 
     * @param externalIdentifier the string representing the UUID of an annotation.
     * @return the xml element representing the list of h-references of the targets of the annotation with "externalIdentifier".
     * @throws IOException if sending an error fails.
     */
    @GET
    @Produces(MediaType.TEXT_XML)
    @Path("{annotationid: " + BackendConstants.regExpIdentifier + "}/targets")
    @Transactional(readOnly = true)
    public JAXBElement<ReferenceList> getAnnotationTargets(@PathParam("annotationid") String externalIdentifier)
            throws IOException {
        Map params = new HashMap();
        try {
            ReferenceList result = (ReferenceList) (new RequestWrappers(this)).wrapRequestResource(params,
                    new GetTargetList(), Resource.ANNOTATION, Access.READ, externalIdentifier);
            if (result != null) {
                return (new ObjectFactory()).createTargetList(result);
            } else {
                return (new ObjectFactory()).createTargetList(new ReferenceList());
            }
        } catch (NotInDataBaseException e1) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, e1.getMessage());
            return (new ObjectFactory()).createReferenceList(new ReferenceList());
        } catch (ForbiddenException e2) {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e2.getMessage());
            return (new ObjectFactory()).createReferenceList(new ReferenceList());
        }
    }

    private class GetTargetList implements ILambda<Map, ReferenceList> {

        @Override
        public ReferenceList apply(Map params) throws NotInDataBaseException {
            return dbDispatcher.getAnnotationTargets((Number) params.get("internalID"));
        }
    }

    /**
     * 
     * @param link the link representing one of the target sources of the annotation.
     * @param matchMode the relation of the actual target-source link to the "link" parameter: "exact", "starts_with", "ends_ith", "contains".
     * @param text the text fragment that must be present in the annotation body.
     * @param access the access mode of the logged in user to the requested annotations.
     * @param namespace not implemented.
     * @param ownerExternalId the external UUID of the owner of requested annotations.
     * @param after the minimal creation/update date of the annotation.
     * @param before the maximal creation/update date of the annotation.
     * @return the xml-element representing the list of {@link AnnotationInfo} objects representing 
     * the annotations satisfying the requirements defined by the parameters.
     * @throws IOException if sending an error fails.
     */
    @GET
    @Produces(MediaType.TEXT_XML)
    @Path("")
    @Transactional(readOnly = true)
    public JAXBElement<AnnotationInfoList> getFilteredAnnotations(@QueryParam("link") String link,
            @QueryParam("matchMode") String matchMode, @QueryParam("text") String text,
            @QueryParam("access") String access, @QueryParam("namespace") String namespace,
            @QueryParam("owner") String ownerExternalId, @QueryParam("after") String after,
            @QueryParam("before") String before) throws IOException {

        Number principalID = this.getPrincipalID();
        if (principalID == null) {
            return new ObjectFactory().createAnnotationInfoList(new AnnotationInfoList());
        }

        if (matchMode == null) {
            matchMode = defaultMatchMode;
        }
        if (!Arrays.asList(admissibleMatchModes).contains(matchMode)) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, "ivalide match mode " + matchMode);
            return new ObjectFactory().createAnnotationInfoList(new AnnotationInfoList());
        }

        UUID ownerExternalUUID = (ownerExternalId != null) ? UUID.fromString(ownerExternalId) : null;

        if (access == null) {
            access = defaultAccess;
        }
        if (!Arrays.asList(admissibleAccess).contains(access)) {
            this.INVALID_ACCESS_MODE(access);
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, "ivalide mode acess " + access);
            return new ObjectFactory().createAnnotationInfoList(new AnnotationInfoList());
        }

        try {
            final AnnotationInfoList annotationInfoList = dbDispatcher.getFilteredAnnotationInfos(ownerExternalUUID,
                    link, MatchMode.valueOf(matchMode.toUpperCase()), text, principalID, access, namespace, after,
                    before);
            return new ObjectFactory().createAnnotationInfoList(annotationInfoList);
        } catch (NotInDataBaseException e) {
            loggerServer.debug(e.toString());
            httpServletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString());
            return new ObjectFactory().createAnnotationInfoList(new AnnotationInfoList());
        }
    }

    /**
     * The request can be sent by principals with "developer" or "admin" account.
     * @param n # of threads.
     * @return a message reporting test success.
     * @throws IOException if sending an error fails.
     * @throws NotInDataBaseException if getting annotation fails.
     */
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("stressTest")
    @Transactional(readOnly = true)
    public String getAnnotationsMultithread(@QueryParam("n") int n) throws IOException, NotInDataBaseException {
        Number remotePrincipalID = this.getPrincipalID();
        if (remotePrincipalID == null) {
            return "You are not logged in";
        }
        String typeOfAccount = dbDispatcher.getTypeOfPrincipalAccount(remotePrincipalID);
        if (typeOfAccount.equals(admin) || typeOfAccount.equals(DebugResource.developer)) {

            System.out.print("Preparing the data: getting the list of all annotations, picking up " + n
                    + " of them randomly, and initializing threads");
            final List<Number> annotationIDs = dbDispatcher.getFilteredAnnotationIDs(null, null, null, null,
                    remotePrincipalID, "read", null, null, null);
            final int size = annotationIDs.size();
            List<GetThread> threads = new ArrayList<GetThread>(n);
            Random rand = new Random();
            for (int i = 0; i < n; i++) {
                int r = rand.nextInt(size);
                String annotationExternalId = dbDispatcher
                        .getResourceExternalIdentifier(annotationIDs.get(r), Resource.ANNOTATION).toString();
                GetThread thread = new GetThread(this, annotationExternalId);
                threads.add(thread);
            }

            System.out.print(
                    "Running on getAnnotation(id) (no serialized output is shown to save time) on randomly selected annotation ids.");
            for (int i = 0; i < n; i++) {
                threads.get(i).run();
            }

            return "Stress-tested annotationrResource's getAnnotation(xxx): ok";
        } else {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
            return "You cannot enjoy this priviledged service because youe are neither admin nor developer. Ask the admin for more priviledges";
        }
    }

    /**
     * 
     * @param externalIdentifier the external UUID of an annotation.
     * @return the xml-element representing the list of permissions, i.e. pairs (principalId, accessMode),
     * built upon the table "annotations_principals_accesses".
     * @throws IOException if sending an error fails.
     */
    @GET
    @Produces(MediaType.TEXT_XML)
    @Path("{annotationid: " + BackendConstants.regExpIdentifier + "}/permissions")
    @Transactional(readOnly = true)
    public JAXBElement<PermissionList> getAnnotationPermissions(
            @PathParam("annotationid") String externalIdentifier) throws IOException {
        Map params = new HashMap();
        try {
            PermissionList result = (PermissionList) (new RequestWrappers(this)).wrapRequestResource(params,
                    new GetPermissionList(), Resource.ANNOTATION, Access.READ, externalIdentifier);
            if (result != null) {
                return (new ObjectFactory()).createPermissionList(result);
            } else {
                return (new ObjectFactory()).createPermissionList(new PermissionList());
            }
        } catch (NotInDataBaseException e1) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, e1.getMessage());
            return (new ObjectFactory()).createPermissionList(new PermissionList());
        } catch (ForbiddenException e2) {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e2.getMessage());
            return (new ObjectFactory()).createPermissionList(new PermissionList());
        }
    }

    private class GetPermissionList implements ILambda<Map, PermissionList> {

        @Override
        public PermissionList apply(Map params) throws NotInDataBaseException {
            return dbDispatcher.getPermissions((Number) params.get("internalID"),
                    (Resource) params.get("resourceType"));
        }
    }

    /**
     * 
     * @param externalIdentifier the external UUID of the annotation to be deleted.
     * @return the message telling if the annotation is deleted or not.
     * @throws IOException if sending an error fails.
     */
    @DELETE
    @Path("{annotationid: " + BackendConstants.regExpIdentifier + "}")
    public String deleteAnnotation(@PathParam("annotationid") String externalIdentifier) throws IOException {
        Map params = new HashMap();
        try {
            int[] result = (int[]) (new RequestWrappers(this)).wrapRequestResource(params, new DeleteAnnotation(),
                    Resource.ANNOTATION, Access.ALL, externalIdentifier);
            if (result != null) {
                return result[0] + " annotation(s) is(are) deleted.";
            } else {
                return "Nothing is deleted.";
            }
        } catch (NotInDataBaseException e1) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, e1.getMessage());
            return e1.getMessage();
        } catch (ForbiddenException e2) {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e2.getMessage());
            return e2.getMessage();
        }
    }

    private class DeleteAnnotation implements ILambda<Map, int[]> {

        @Override
        public int[] apply(Map params) throws NotInDataBaseException {
            return dbDispatcher.deleteAnnotation((Number) params.get("internalID"));
        }
    }

    /**
     * 
     * @param annotation an {@link Annotation} object.
     * @return the {@link ResponseBody} element that contains the xml element representing 
     * the fresh annotation (with its freshly generated by the back-end external UUID), and the list
     * of action-elements representing the actions the client should care for,
     * e.g. add a cached representation for a certain target.
     * @throws IOException if sending an error fails.
     */
    @POST
    @Consumes(MediaType.APPLICATION_XML)
    @Produces(MediaType.APPLICATION_XML)
    @Path("")
    public JAXBElement<ResponseBody> createAnnotation(Annotation annotation) throws IOException {

        Map params = new HashMap();
        params.put("annotation", annotation);
        try {
            ResponseBody result = (ResponseBody) (new RequestWrappers(this)).wrapRequestResource(params,
                    new AddAnnotation());
            if (result != null) {
                return (new ObjectFactory()).createResponseBody(result);
            } else {
                return (new ObjectFactory()).createResponseBody(new ResponseBody());
            }
        } catch (NotInDataBaseException e) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
            return (new ObjectFactory()).createResponseBody(new ResponseBody());
        } catch (ForbiddenException e2) {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e2.getMessage());
            return (new ObjectFactory()).createResponseBody(new ResponseBody());
        }
    }

    private class AddAnnotation implements ILambda<Map, ResponseBody> {

        @Override
        public ResponseBody apply(Map params) throws NotInDataBaseException {
            Number principalID = (Number) params.get("principalID");
            Annotation annotation = (Annotation) params.get("annotation");
            Number annotationID = dbDispatcher.addPrincipalsAnnotation(principalID, annotation);
            return dbDispatcher.makeAnnotationResponseEnvelope(annotationID);
        }
    }

    /**
     * 
     * @param externalId the external UUID of the annotation to be updated.
     * @param annotation the {@link Annotation} object representing the new annotation that should replace the annotation with "externalId".
     * @return the {@link ResponseBody} element that contains the xml element representing 
      * the updated annotation, and the list of action-elements representing the actions the client should care for,
      * e.g. add a cached representation for a certain target.
      * @throws IOException if sending an error fails. 
     */
    @PUT
    @Consumes(MediaType.APPLICATION_XML)
    @Produces(MediaType.APPLICATION_XML)
    @Path("{annotationid: " + BackendConstants.regExpIdentifier + "}")
    public JAXBElement<ResponseBody> updateAnnotation(@PathParam("annotationid") String externalId,
            Annotation annotation) throws IOException {

        String annotationExtId = annotation.getId();
        if (!(externalId).equals(annotationExtId)) {
            loggerServer.debug("Wrong request: the annotation identifier   " + externalId
                    + " and the annotation (notebook) ID from the request body do not match.");
            httpServletResponse.sendError(HttpServletResponse.SC_BAD_REQUEST);
            return null;
        }
        try {
            Map params = new HashMap();
            params.put("annotation", annotation);
            params.put("remoteUser", httpServletRequest.getRemoteUser());
            ResponseBody result = (ResponseBody) (new RequestWrappers(this)).wrapRequestResource(params,
                    new UpdateAnnotation(), Resource.ANNOTATION, Access.WRITE, externalId);
            if (result != null) {
                return (new ObjectFactory()).createResponseBody(result);
            } else {
                return (new ObjectFactory()).createResponseBody(new ResponseBody());
            }
        } catch (NotInDataBaseException e1) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, e1.getMessage());
            return (new ObjectFactory()).createResponseBody(new ResponseBody());
        } catch (ForbiddenException e2) {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e2.getMessage());
            return (new ObjectFactory()).createResponseBody(new ResponseBody());
        }
    }

    private class UpdateAnnotation implements ILambda<Map, ResponseBody> {

        @Override
        public ResponseBody apply(Map params) throws NotInDataBaseException, ForbiddenException {
            Annotation annotation = (Annotation) params.get("annotation");
            Number annotationID = (Number) params.get("internalID");
            String remoteUser = (String) params.get("remoteUser");
            int updatedRows = dbDispatcher.updateAnnotation(annotation, remoteUser);
            return dbDispatcher.makeAnnotationResponseEnvelope(annotationID);
        }
    }

    /**
     * 
     * @param externalIdentifier the external UUID of the annotation whose body must be updated.
     * @param annotationBody an {@link AnnotationBody} object representation the new body of the annotation,
     * which should replace the old body.
     * @return the {@link ResponseBody} element that contains the xml element representing 
     * the updated annotation, and the list of action-elements representing the actions the client should care for.
     * @throws IOException if sending an error fails.  
     */
    @PUT
    @Consumes(MediaType.APPLICATION_XML)
    @Produces(MediaType.APPLICATION_XML)
    @Path("{annotationid: " + BackendConstants.regExpIdentifier + "}/body")
    public JAXBElement<ResponseBody> updateAnnotationBody(@PathParam("annotationid") String externalIdentifier,
            AnnotationBody annotationBody) throws IOException {
        Map params = new HashMap();
        params.put("annotationBody", annotationBody);
        try {
            ResponseBody result = (ResponseBody) (new RequestWrappers(this)).wrapRequestResource(params,
                    new UpdateAnnotationBody(), Resource.ANNOTATION, Access.WRITE, externalIdentifier);
            if (result != null) {
                return (new ObjectFactory()).createResponseBody(result);
            } else {
                return (new ObjectFactory()).createResponseBody(new ResponseBody());
            }
        } catch (NotInDataBaseException e1) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, e1.getMessage());
            return (new ObjectFactory()).createResponseBody(new ResponseBody());
        } catch (ForbiddenException e2) {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e2.getMessage());
            return (new ObjectFactory()).createResponseBody(new ResponseBody());
        }

    }

    ///////////////////////////////////////////////////////////
    private class UpdateAnnotationBody implements ILambda<Map, ResponseBody> {

        @Override
        public ResponseBody apply(Map params) throws NotInDataBaseException {
            Number resourceID = (Number) params.get("internalID");
            AnnotationBody annotationBody = (AnnotationBody) params.get("annotationBody");
            int updatedRows = dbDispatcher.updateAnnotationBody(resourceID, annotationBody);
            return dbDispatcher.makeAnnotationResponseEnvelope(resourceID);
        }
    }

    /**
     * 
     * @param externalIdentifier the external UUID of the annotation whose headline is to be updated.
     * @param newHeadline a new headline.
     * @return the {@link ResponseBody} element that contains the xml element representing 
     * the updated annotation, and the list of action-elements representing the actions the client should care for.
     * @throws IOException if sending an error fails.  
     */
    @PUT
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.APPLICATION_XML)
    @Path("{annotationid: " + BackendConstants.regExpIdentifier + "}/headline")
    public JAXBElement<ResponseBody> updateAnnotationHeadline(@PathParam("annotationid") String externalIdentifier,
            String newHeadline) throws IOException {
        Map params = new HashMap();
        params.put("headline", newHeadline);
        try {
            ResponseBody result = (ResponseBody) (new RequestWrappers(this)).wrapRequestResource(params,
                    new UpdateAnnotationHeadline(), Resource.ANNOTATION, Access.WRITE, externalIdentifier);
            if (result != null) {
                return (new ObjectFactory()).createResponseBody(result);
            } else {
                return (new ObjectFactory()).createResponseBody(new ResponseBody());
            }
        } catch (NotInDataBaseException e1) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, e1.getMessage());
            return (new ObjectFactory()).createResponseBody(new ResponseBody());
        } catch (ForbiddenException e2) {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e2.getMessage());
            return (new ObjectFactory()).createResponseBody(new ResponseBody());
        }

    }

    ///////////////////////////////////////////////////////////
    private class UpdateAnnotationHeadline implements ILambda<Map, ResponseBody> {

        @Override
        public ResponseBody apply(Map params) throws NotInDataBaseException {
            Number resourceID = (Number) params.get("internalID");
            String newHeadline = (String) params.get("headline");
            int updatedRows = dbDispatcher.updateAnnotationHeadline(resourceID, newHeadline);
            return dbDispatcher.makeAnnotationResponseEnvelope(resourceID);
        }
    }

    /**
     * 
     * @param annotationDatabaseId the internal database Id of the annotation to be updated.
     * @param annotationHeadline the annotation's headline.
     * @param access the new value for the public attribute of the annotation.
     * @return the message telling if the database has been updated and how many rows have been updated;
     * if "annotationDatabaseId" == null or empty then all the annotations with "annotationHeadline"
     * must be updated.
     * @throws IOException if sending an error fails.
     */
    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.TEXT_PLAIN)
    @Path("publicaccessform")
    public String updatePubliAccessFromForm(@FormParam("annotationId") String annotationDatabaseId,
            @FormParam("annotationHeadline") String annotationHeadline, @FormParam("access") String access)
            throws IOException {

        if (access.trim().equals("")) {
            access = "none";
        }

        try {

            Access accessTyped = Access.fromValue(access);
            if (annotationDatabaseId == null || annotationDatabaseId.trim().equals("")) {
                List<UUID> annotationIds = dbDispatcher.getAnnotationExternalIdsFromHeadline(annotationHeadline);
                if (annotationIds == null || annotationIds.isEmpty()) {
                    httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND,
                            "No annotations with this headline have been found");
                    return "No annotations with this headline have been found";
                }
                ;
                int updatedAnnotations = 0;
                for (UUID annotationId : annotationIds) {
                    Map params = new HashMap();
                    params.put("access", accessTyped);
                    Integer result = (Integer) (new RequestWrappers(this)).wrapRequestResource(params,
                            new UpdatePublicAccess(), Resource.ANNOTATION, Access.ALL, annotationId.toString());
                    updatedAnnotations = (result != null) ? updatedAnnotations + result.intValue()
                            : updatedAnnotations;
                }
                return (updatedAnnotations + " row(s) are updated");
            } else {
                Map params = new HashMap();
                params.put("access", accessTyped);
                Integer result = (Integer) (new RequestWrappers(this)).wrapRequestResource(params,
                        new UpdatePublicAccess(), Resource.ANNOTATION, Access.ALL, annotationDatabaseId);
                if (result != null) {
                    return result + " row(s) is(are) updated.";
                } else {
                    return "0 rows are updated.";
                }
            }
        } catch (NotInDataBaseException e1) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, e1.toString());
            return "0 rows are updated.";
        } catch (ForbiddenException e2) {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e2.toString());
            return "0 rows are updated.";
        }
    }

    private class UpdatePublicAccess implements ILambda<Map, Integer> {

        @Override
        public Integer apply(Map params) throws NotInDataBaseException {
            Number annotationID = (Number) params.get("internalID");
            Access access = (Access) params.get("access");
            return dbDispatcher.updatePublicAttribute(annotationID, access);
        }
    }

    /**
     * 
     * @param annotationExternalId the external UUID of an annotation.
     * @param principalExternalId the external UUID of a principal whose access mode to the annotation must be updated.
     * @param access the access mode that should be assigned to the principal.
     * @return the message about the amount of updated rows.
     * @throws IOException if sending an error fails.
     */
    @PUT
    @Consumes(MediaType.APPLICATION_XML)
    @Produces(MediaType.APPLICATION_XML)
    @Path("{annotationid: " + BackendConstants.regExpIdentifier + "}/permissions/{principalid: "
            + BackendConstants.regExpIdentifier + "}")
    public String updatePermission(@PathParam("annotationid") String annotationExternalId,
            @PathParam("principalid") String principalExternalId, Access access) throws IOException {
        try {
            return this.genericUpdateDeletePermission(annotationExternalId, principalExternalId, access);
        } catch (NotInDataBaseException e1) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, e1.getMessage());
            return e1.getMessage();
        } catch (ForbiddenException e2) {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e2.getMessage());
            return e2.getMessage();
        }
    }

    /**
     * One of 3 principal-related parameters must be non-null and non-empty; one of 2 annotation-related parameters must be non-null and non-empty.
     * @param remoteID the remote ID of the principal whose access mode to the annotation (see below) must be updated.
     * @param fullName the full name of the principal whose access mode to the annotation must be updated.
     * @param principalDatabaseId the internal database identifier of the principal whose access mode to the annotation must be updated. 
     * @param annotationDatabaseId the internal database id of the annotation for which the access mode must be updated.
     * @param annotationHeadline the headline of the annotation for which the access mode must be updated.
     * @param access the new access mode.
     * @return the message explaining how many rows have been updated. 
     * @throws IOException  if sending an error fails.
     */
    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.TEXT_PLAIN)
    @Path("permissionform")
    public String updatePermissionFromForm(@FormParam("login") String remoteID,
            @FormParam("fullName") String fullName, @FormParam("userId") String principalDatabaseId,
            @FormParam("annotationId") String annotationDatabaseId,
            @FormParam("annotationHeadline") String annotationHeadline, @FormParam("access") String access)
            throws IOException {

        try {

            if (access.trim().equals("")) {
                access = null;
            }

            if (principalDatabaseId == null || principalDatabaseId.trim().equals("")) {
                if (remoteID != null && !remoteID.trim().equals("")) {
                    principalDatabaseId = dbDispatcher.getPrincipalExternalIDFromRemoteID(remoteID).toString();
                } else {
                    if (fullName != null) {
                        principalDatabaseId = dbDispatcher.getPrincipalExternalIdFromName(fullName).toString();
                    } else {
                        httpServletResponse.sendError(HttpServletResponse.SC_BAD_REQUEST,
                                "No user information is given");
                    }
                }
            }

            if (annotationDatabaseId == null || annotationDatabaseId.trim().equals("")) {
                List<UUID> annotationIds = dbDispatcher.getAnnotationExternalIdsFromHeadline(annotationHeadline);
                if (annotationIds == null || annotationIds.isEmpty()) {
                    return "No annotations with this headline found";
                }
                ;
                int count = 0;
                String tmp = null;
                for (UUID annotationId : annotationIds) {
                    tmp = this.genericUpdateDeletePermission(annotationId.toString(), principalDatabaseId,
                            Access.fromValue(access));
                    if (!tmp.startsWith("0")) {
                        count++;
                    }
                }
                return (count + " row(s) are updated");
            } else {
                return this.genericUpdateDeletePermission(annotationDatabaseId, principalDatabaseId,
                        Access.fromValue(access));
            }

        } catch (NotInDataBaseException e1) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, e1.getMessage());
            return e1.getMessage();
        } catch (ForbiddenException e2) {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e2.getMessage());
            return e2.getMessage();
        }
    }

    ////////////////////////////////////////////
    private String genericUpdateDeletePermission(String annotationId, String principalId, Access access)
            throws IOException, NotInDataBaseException, ForbiddenException {
        Map params = new HashMap();
        params.put("access", access);
        final Number inputPrincipalID = dbDispatcher.getResourceInternalIdentifier(UUID.fromString(principalId),
                Resource.PRINCIPAL);
        params.put("inputPrincipalID", inputPrincipalID);
        Integer result = (Integer) (new RequestWrappers(this)).wrapRequestResource(params,
                new UpdatePermissionHelper(), Resource.ANNOTATION, Access.ALL, annotationId);
        if (result != null) {
            return result + " row(s) is(are) updated.";
        } else {
            return "0 rows are updated.";
        }
    }

    private class UpdatePermissionHelper implements ILambda<Map, Integer> {

        @Override
        public Integer apply(Map params) throws NotInDataBaseException {
            Number annotationID = (Number) params.get("internalID");
            Number principalID = (Number) params.get("inputPrincipalID");
            Access access = (Access) params.get("access");
            return dbDispatcher.updatePermission(annotationID, principalID, access);
        }
    }

    /**
     * 
     * @param annotationExternalId the external UUID of an annotation.
     * @param permissions a {@link PermissionList} object representing a list of pairs (principal UUID, access mode) of the
     * new list of permissions for the annotation.
     * @return the {@link ResponseBody} element that contains the xml element representing 
     * the updated annotation, and the list of action-elements representing the actions the client should care for,
     * e.g. add the e-mail of a certain principal.
     * @throws IOException if sending an error fails.
     */
    @PUT
    @Consumes(MediaType.APPLICATION_XML)
    @Produces(MediaType.APPLICATION_XML)
    @Path("{annotationid: " + BackendConstants.regExpIdentifier + "}/permissions/")
    public JAXBElement<ResponseBody> updatePermissions(@PathParam("annotationid") String annotationExternalId,
            PermissionList permissions) throws IOException {

        Map params = new HashMap();
        params.put("permissions", permissions);
        try {
            ResponseBody result = (ResponseBody) (new RequestWrappers(this)).wrapRequestResource(params,
                    new UpdatePermissions(), Resource.ANNOTATION, Access.ALL, annotationExternalId);
            if (result != null) {
                return new ObjectFactory().createResponseBody(result);
            } else {
                return new ObjectFactory().createResponseBody(new ResponseBody());
            }
        } catch (NotInDataBaseException e1) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, e1.getMessage());
            return new ObjectFactory().createResponseBody(new ResponseBody());
        } catch (ForbiddenException e2) {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e2.getMessage());
            return new ObjectFactory().createResponseBody(new ResponseBody());
        }

    }

    ////////////////////////////////////////////
    private class UpdatePermissions implements ILambda<Map, ResponseBody> {

        @Override
        public ResponseBody apply(Map params) throws NotInDataBaseException {
            Number annotationID = (Number) params.get("internalID");
            PermissionList permissions = (PermissionList) params.get("permissions");
            int updatedRows = dbDispatcher.updateOrAddPermissions(annotationID, permissions);
            return dbDispatcher.makeAccessResponseEnvelope(annotationID, Resource.ANNOTATION);
        }
    }

    /**
     * 
     * @param annotationId the external UUID of the annotation.
     * @param principalId the external UUID of a principal whose access mode must be deleted.
     * @return the amount of removed rows in the database.
     * @throws IOException if sending an error fails.
     */
    @DELETE
    @Produces(MediaType.TEXT_PLAIN)
    @Path("{annotationId: " + BackendConstants.regExpIdentifier + "}/principal/{principalId}/delete")
    public String deletePermission(@PathParam("annotationId") String annotationId,
            @PathParam("principalId") String principalId) throws IOException {
        try {
            return this.genericUpdateDeletePermission(annotationId, principalId, null);
        } catch (NotInDataBaseException e1) {
            httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, e1.getMessage());
            return e1.getMessage();
        } catch (ForbiddenException e2) {
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e2.getMessage());
            return e2.getMessage();
        }
    }

}