org.fao.geonet.api.records.MetadataWorkflowApi.java Source code

Java tutorial

Introduction

Here is the source code for org.fao.geonet.api.records.MetadataWorkflowApi.java

Source

/*
 * Copyright (C) 2001-2016 Food and Agriculture Organization of the
 * United Nations (FAO-UN), United Nations World Food Programme (WFP)
 * and United Nations Environment Programme (UNEP)
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
 * Rome - Italy. email: geonetwork@osgeo.org
 */

package org.fao.geonet.api.records;

import static org.fao.geonet.api.ApiParams.API_CLASS_RECORD_OPS;
import static org.fao.geonet.api.ApiParams.API_CLASS_RECORD_TAG;
import static org.fao.geonet.api.ApiParams.API_PARAM_RECORD_UUID;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.fao.geonet.ApplicationContextHolder;
import org.fao.geonet.api.API;
import org.fao.geonet.api.ApiParams;
import org.fao.geonet.api.ApiUtils;
import org.fao.geonet.api.exception.ResourceNotFoundException;
import org.fao.geonet.api.records.model.MetadataStatusParameter;
import org.fao.geonet.api.records.model.MetadataStatusResponse;
import org.fao.geonet.api.records.model.MetadataWorkflowStatusResponse;
import org.fao.geonet.api.tools.i18n.LanguageUtils;
import org.fao.geonet.domain.AbstractMetadata;
import org.fao.geonet.domain.ISODate;
import org.fao.geonet.domain.MetadataStatus;
import org.fao.geonet.domain.MetadataStatusId;
import org.fao.geonet.domain.MetadataStatusId_;
import org.fao.geonet.domain.MetadataStatus_;
import org.fao.geonet.domain.Pair;
import org.fao.geonet.domain.Profile;
import org.fao.geonet.domain.StatusValue;
import org.fao.geonet.domain.StatusValueType;
import org.fao.geonet.domain.User;
import org.fao.geonet.domain.User_;
import org.fao.geonet.domain.utils.ObjectJSONUtils;
import org.fao.geonet.kernel.AccessManager;
import org.fao.geonet.kernel.DataManager;
import org.fao.geonet.kernel.datamanager.IMetadataStatus;
import org.fao.geonet.kernel.metadata.StatusActions;
import org.fao.geonet.kernel.metadata.StatusActionsFactory;
import org.fao.geonet.kernel.search.LuceneSearcher;
import org.fao.geonet.repository.MetadataStatusRepository;
import org.fao.geonet.repository.MetadataStatusRepositoryCustom;
import org.fao.geonet.repository.SortUtils;
import org.fao.geonet.repository.StatusValueRepository;
import org.fao.geonet.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import com.sun.istack.NotNull;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import jeeves.server.context.ServiceContext;
import jeeves.services.ReadWriteController;

@RequestMapping(value = { "/api/records", "/api/" + API.VERSION_0_1 + "/records" })
@Api(value = API_CLASS_RECORD_TAG, tags = API_CLASS_RECORD_TAG, description = API_CLASS_RECORD_OPS)
@Controller("recordWorkflow")
@ReadWriteController
public class MetadataWorkflowApi {

    @Autowired
    LanguageUtils languageUtils;

    @ApiOperation(value = "Get record status history", notes = "", nickname = "getRecordStatusHistory")
    @RequestMapping(value = "/{metadataUuid}/status", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    public List<MetadataStatusResponse> getRecordStatusHistory(
            @ApiParam(value = API_PARAM_RECORD_UUID, required = true) @PathVariable String metadataUuid,
            @RequestParam(required = false) boolean details,
            @ApiParam(value = "Sort direction", required = false) @RequestParam(defaultValue = "DESC") Sort.Direction sortOrder,
            HttpServletRequest request) throws Exception {
        ServiceContext context = ApiUtils.createServiceContext(request);
        AbstractMetadata metadata = ApiUtils.canViewRecord(metadataUuid, request);
        MetadataStatusRepositoryCustom metadataStatusRepository = ApplicationContextHolder.get()
                .getBean(MetadataStatusRepository.class);

        String sortField = SortUtils.createPath(MetadataStatus_.id, MetadataStatusId_.changeDate);

        List<MetadataStatus> listOfStatus = ((MetadataStatusRepository) metadataStatusRepository)
                .findAllById_MetadataId(metadata.getId(), new Sort(sortOrder, sortField));

        List<MetadataStatusResponse> response = buildMetadataStatusResponses(listOfStatus, details,
                context.getLanguage());

        // TODO: Add paging
        return response;
    }

    @ApiOperation(value = "Get record status history by type", notes = "", nickname = "getRecordStatusHistoryByType")
    @RequestMapping(value = "/{metadataUuid}/status/{type}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    public List<MetadataStatusResponse> getRecordStatusHistoryByType(
            @ApiParam(value = API_PARAM_RECORD_UUID, required = true) @PathVariable String metadataUuid,
            @ApiParam(value = "Type", required = true) @PathVariable StatusValueType type,
            @RequestParam(required = false) boolean details,
            @ApiParam(value = "Sort direction", required = false) @RequestParam(defaultValue = "DESC") Sort.Direction sortOrder,
            HttpServletRequest request) throws Exception {
        ServiceContext context = ApiUtils.createServiceContext(request);
        AbstractMetadata metadata = ApiUtils.canViewRecord(metadataUuid, request);
        MetadataStatusRepositoryCustom metadataStatusRepository = ApplicationContextHolder.get()
                .getBean(MetadataStatusRepository.class);

        String sortField = SortUtils.createPath(MetadataStatus_.id, MetadataStatusId_.changeDate);

        List<MetadataStatus> listOfStatus = metadataStatusRepository.findAllByIdAndByType(metadata.getId(), type,
                new Sort(sortOrder, sortField));

        List<MetadataStatusResponse> response = buildMetadataStatusResponses(listOfStatus, details,
                context.getLanguage());

        // TODO: Add paging
        return response;
    }

    @ApiOperation(value = "Get last workflow status for a record", notes = "", nickname = "getStatus")
    @RequestMapping(value = "/{metadataUuid}/status/workflow/last", method = RequestMethod.GET, produces = {
            MediaType.APPLICATION_JSON_VALUE })
    @PreAuthorize("hasRole('Editor')")
    @ApiResponses(value = { @ApiResponse(code = 200, message = "Record status."),
            @ApiResponse(code = 403, message = ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_EDIT) })
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public MetadataWorkflowStatusResponse getStatus(
            @ApiParam(value = API_PARAM_RECORD_UUID, required = true) @PathVariable String metadataUuid,
            HttpServletRequest request) throws Exception {
        AbstractMetadata metadata = ApiUtils.canEditRecord(metadataUuid, request);
        ApplicationContext appContext = ApplicationContextHolder.get();
        Locale locale = languageUtils.parseAcceptLanguage(request.getLocales());
        ServiceContext context = ApiUtils.createServiceContext(request, locale.getISO3Language());

        AccessManager am = appContext.getBean(AccessManager.class);
        //--- only allow the owner of the record to set its status
        if (!am.isOwner(context, String.valueOf(metadata.getId()))) {
            throw new SecurityException(String.format(
                    "Only the owner of the metadata can get the status. User is not the owner of the metadata"));
        }

        IMetadataStatus metadataStatus = context.getBean(IMetadataStatus.class);
        MetadataStatus recordStatus = metadataStatus.getStatus(metadata.getId());

        //        List<StatusValue> elStatus = context.getBean(StatusValueRepository.class).findAll();
        List<StatusValue> elStatus = context.getBean(StatusValueRepository.class)
                .findAllByType(StatusValueType.workflow);

        //--- get the list of content reviewers for this metadata record
        Set<Integer> ids = new HashSet<Integer>();
        ids.add(Integer.valueOf(metadata.getId()));
        List<Pair<Integer, User>> reviewers = context.getBean(UserRepository.class)
                .findAllByGroupOwnerNameAndProfile(ids, Profile.Reviewer, SortUtils.createSort(User_.name));
        List<User> listOfReviewers = new ArrayList<>();
        for (Pair<Integer, User> reviewer : reviewers) {
            listOfReviewers.add(reviewer.two());
        }
        return new MetadataWorkflowStatusResponse(recordStatus, listOfReviewers,
                am.hasEditPermission(context, metadata.getId() + ""), elStatus);

    }

    @ApiOperation(value = "Set the record status", notes = "", nickname = "setStatus")
    @RequestMapping(value = "/{metadataUuid}/status", method = RequestMethod.PUT)
    @PreAuthorize("hasRole('Editor')")
    @ApiResponses(value = { @ApiResponse(code = 204, message = "Status updated."),
            @ApiResponse(code = 403, message = ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_EDIT) })
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void setStatus(
            @ApiParam(value = API_PARAM_RECORD_UUID, required = true) @PathVariable String metadataUuid,
            @ApiParam(value = "Metadata status", required = true) @RequestBody(required = true) MetadataStatusParameter status,
            HttpServletRequest request) throws Exception {
        AbstractMetadata metadata = ApiUtils.canEditRecord(metadataUuid, request);
        ApplicationContext appContext = ApplicationContextHolder.get();
        ServiceContext context = ApiUtils.createServiceContext(request,
                languageUtils.getIso3langCode(request.getLocales()));

        AccessManager am = appContext.getBean(AccessManager.class);
        //--- only allow the owner of the record to set its status
        if (!am.isOwner(context, String.valueOf(metadata.getId()))) {
            throw new SecurityException(String.format(
                    "Only the owner of the metadata can set the status of this record. User is not the owner of the metadata."));
        }

        //--- use StatusActionsFactory and StatusActions class to
        //--- change status and carry out behaviours for status changes
        StatusActionsFactory saf = appContext.getBean(StatusActionsFactory.class);

        StatusActions sa = saf.createStatusActions(context);

        int author = context.getUserSession().getUserIdAsInt();
        MetadataStatus metadataStatus = convertParameter(metadata.getId(), status, author);
        List<MetadataStatus> listOfStatusChange = new ArrayList<>(1);
        listOfStatusChange.add(metadataStatus);
        sa.onStatusChange(listOfStatusChange);

        //--- reindex metadata
        DataManager dataManager = appContext.getBean(DataManager.class);
        dataManager.indexMetadata(String.valueOf(metadata.getId()), true, null);
    }

    @ApiOperation(value = "Close a record task", notes = "", nickname = "closeTask")
    @RequestMapping(value = "/{metadataUuid}/status/{statusId:[0-9]+}.{userId:[0-9]+}.{changeDate}/close", method = RequestMethod.PUT)
    @PreAuthorize("hasRole('Editor')")
    @ApiResponses(value = { @ApiResponse(code = 204, message = "Task closed."),
            @ApiResponse(code = 404, message = "Status not found."),
            @ApiResponse(code = 403, message = ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_EDIT) })
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void closeTask(
            @ApiParam(value = API_PARAM_RECORD_UUID, required = true) @PathVariable String metadataUuid,
            @ApiParam(value = "Status identifier", required = true) @PathVariable int statusId,
            @ApiParam(value = "User identifier", required = true) @PathVariable int userId,
            @ApiParam(value = "Change date", required = true) @PathVariable String changeDate,
            @ApiParam(value = "Close date", required = true) @RequestParam String closeDate,
            HttpServletRequest request) throws Exception {
        AbstractMetadata metadata = ApiUtils.canEditRecord(metadataUuid, request);

        MetadataStatusRepository statusRepository = ApplicationContextHolder.get()
                .getBean(MetadataStatusRepository.class);
        MetadataStatus metadataStatus = statusRepository
                .findOne(new MetadataStatusId().setMetadataId(metadata.getId()).setStatusId(statusId)
                        .setUserId(userId).setChangeDate(new ISODate(changeDate)));
        if (metadataStatus != null) {
            statusRepository.update(metadataStatus.getId(), entity -> entity.setCloseDate(new ISODate(closeDate)));
        } else {
            throw new ResourceNotFoundException(
                    String.format("Can't find metadata status for record '%d', user '%s' at date '%s'",
                            metadataUuid, userId, changeDate));
        }
    }

    @ApiOperation(value = "Delete a record status", notes = "", nickname = "deleteStatus")
    @RequestMapping(value = "/{metadataUuid}/status/{statusId:[0-9]+}.{userId:[0-9]+}.{changeDate}", method = RequestMethod.DELETE)
    @PreAuthorize("hasRole('Administrator')")
    @ApiResponses(value = { @ApiResponse(code = 204, message = "Status removed."),
            @ApiResponse(code = 404, message = "Status not found."),
            @ApiResponse(code = 403, message = ApiParams.API_RESPONSE_NOT_ALLOWED_ONLY_ADMIN) })
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteRecordStatus(
            @ApiParam(value = API_PARAM_RECORD_UUID, required = true) @PathVariable String metadataUuid,
            @ApiParam(value = "Status identifier", required = true) @PathVariable int statusId,
            @ApiParam(value = "User identifier", required = true) @PathVariable int userId,
            @ApiParam(value = "Change date", required = true) @PathVariable String changeDate,
            HttpServletRequest request) throws Exception {
        AbstractMetadata metadata = ApiUtils.canEditRecord(metadataUuid, request);

        MetadataStatusRepository statusRepository = ApplicationContextHolder.get()
                .getBean(MetadataStatusRepository.class);
        MetadataStatus metadataStatus = statusRepository
                .findOne(new MetadataStatusId().setMetadataId(metadata.getId()).setStatusId(statusId)
                        .setUserId(userId).setChangeDate(new ISODate(changeDate)));
        if (metadataStatus != null) {
            statusRepository.delete(metadataStatus);
            // TODO: Reindex record ?
        } else {
            throw new ResourceNotFoundException(
                    String.format("Can't find metadata status for record '%d', user '%s' at date '%s'",
                            metadataUuid, userId, changeDate));
        }
    }

    @ApiOperation(value = "Search status", notes = "", nickname = "searchStatusByType")
    @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET, path = "/status/search")
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    public List<MetadataStatusResponse> getStatusByType(
            @ApiParam(value = "One or more types to retrieve (ie. worflow, event, task). Default is all.", required = false) @RequestParam(required = false) StatusValueType[] type,
            @ApiParam(value = "All event details including XML changes. Responses are bigger. Default is false", required = false) @RequestParam(required = false) boolean details,
            @ApiParam(value = "One or more event author. Default is all.", required = false) @RequestParam(required = false) Integer[] author,
            @ApiParam(value = "One or more event owners. Default is all.", required = false) @RequestParam(required = false) Integer[] owner,
            @ApiParam(value = "One or more record identifier. Default is all.", required = false) @RequestParam(required = false) Integer[] record,
            @ApiParam(value = "Start date", required = false) @RequestParam(required = false) String dateFrom,
            @ApiParam(value = "End date", required = false) @RequestParam(required = false) String dateTo,
            @ApiParam(value = "From page", required = false) @RequestParam(required = false, defaultValue = "0") Integer from,
            @ApiParam(value = "Number of records to return", required = false) @RequestParam(required = false, defaultValue = "100") Integer size,
            HttpServletRequest request) throws Exception {
        ServiceContext context = ApiUtils.createServiceContext(request);
        MetadataStatusRepository statusRepository = context.getBean(MetadataStatusRepository.class);

        Sort sortByStatusChangeDate = SortUtils.createSort(Sort.Direction.DESC, MetadataStatus_.id,
                MetadataStatusId_.changeDate);
        final PageRequest pageRequest = new PageRequest(from, size, sortByStatusChangeDate);

        List<MetadataStatus> metadataStatuses;
        if ((type != null && type.length > 0) || (author != null && author.length > 0)
                || (owner != null && owner.length > 0) || (record != null && record.length > 0)) {
            metadataStatuses = statusRepository.searchStatus(
                    type != null && type.length > 0 ? Arrays.asList(type) : null,
                    author != null && author.length > 0 ? Arrays.asList(author) : null,
                    owner != null && owner.length > 0 ? Arrays.asList(owner) : null,
                    record != null && record.length > 0 ? Arrays.asList(record) : null, dateFrom, dateTo,
                    pageRequest);
        } else {
            metadataStatuses = statusRepository.findAll(pageRequest).getContent();
        }

        return buildMetadataStatusResponses(metadataStatuses, details, context.getLanguage());
    }

    /**
     * Convert request parameter to a metadata status.
     */
    public MetadataStatus convertParameter(int id, MetadataStatusParameter parameter, int author) throws Exception {
        StatusValueRepository statusValueRepository = ApplicationContextHolder.get()
                .getBean(StatusValueRepository.class);
        StatusValue statusValue = statusValueRepository.findOne(parameter.getStatus());

        MetadataStatus metadataStatus = new MetadataStatus();

        MetadataStatusId mdStatusId = new MetadataStatusId().setStatusId(parameter.getStatus()).setMetadataId(id)
                .setChangeDate(new ISODate()).setUserId(author);

        metadataStatus.setId(mdStatusId);
        metadataStatus.setStatusValue(statusValue);

        if (parameter.getChangeMessage() != null) {
            metadataStatus.setChangeMessage(parameter.getChangeMessage());
        }
        if (StringUtils.isNotEmpty(parameter.getDueDate())) {
            metadataStatus.setDueDate(new ISODate(parameter.getDueDate()));
        }
        if (StringUtils.isNotEmpty(parameter.getCloseDate())) {
            metadataStatus.setCloseDate(new ISODate(parameter.getCloseDate()));
        }
        if (parameter.getOwner() != null) {
            metadataStatus.setOwner(parameter.getOwner());
        }
        return metadataStatus;
    }

    /**
     * Build a list of status with additional information about users
     * (author and owner of the status change).
     *
     */
    @NotNull
    private List<MetadataStatusResponse> buildMetadataStatusResponses(List<MetadataStatus> listOfStatus,
            boolean details, String language) {
        List<MetadataStatusResponse> response = new ArrayList<>();

        // Add all user info in response
        Map<Integer, User> listOfUsers = new HashMap<>();
        UserRepository userRepository = ApplicationContextHolder.get().getBean(UserRepository.class);

        // Collect all user info
        for (MetadataStatus s : listOfStatus) {
            if (listOfUsers.get(s.getId().getUserId()) == null) {
                listOfUsers.put(s.getId().getUserId(), userRepository.findOne(s.getId().getUserId()));
            }
            if (s.getOwner() != null && listOfUsers.get(s.getOwner()) == null) {
                listOfUsers.put(s.getOwner(), userRepository.findOne(s.getOwner()));
            }
        }

        Map<Integer, String> titles = new HashMap<>();

        // Add all user info and record title to response
        for (MetadataStatus s : listOfStatus) {
            MetadataStatusResponse status = new MetadataStatusResponse(s, details);

            User author = listOfUsers.get(status.getId().getUserId());
            if (author != null) {
                status.setAuthorName(author.getName() + " " + author.getSurname());
                status.setAuthorEmail(author.getEmail());
            }
            if (s.getOwner() != null) {
                User owner = listOfUsers.get(status.getOwner());
                if (owner != null) {
                    status.setOwnerName(owner.getName() + " " + owner.getSurname());
                    status.setOwnerEmail(owner.getEmail());
                }
            }

            if (s.getStatusValue().getType().equals(StatusValueType.event)) {
                status.setCurrentStatus(extractCurrentStatus(s));
                status.setPreviousStatus(extractPreviousStatus(s));
            }

            String title = titles.get(s.getId().getMetadataId());
            if (title == null) {
                try {
                    // Collect metadata titles. For now we use Lucene
                    title = LuceneSearcher.getMetadataFromIndexById(language, s.getId().getMetadataId() + "",
                            "title");
                    titles.put(s.getId().getMetadataId(), title);
                } catch (Exception e1) {
                }
            }
            status.setTitle(title);

            response.add(status);
        }

        return response;
    }

    private String extractCurrentStatus(MetadataStatus s) {
        switch (Integer.toString(s.getStatusValue().getId())) {
        case StatusValue.Events.ATTACHMENTADDED:
            return s.getCurrentState();
        case StatusValue.Events.RECORDOWNERCHANGE:
            return ObjectJSONUtils.extractFieldFromJSONString(s.getCurrentState(), "owner", "name");
        case StatusValue.Events.RECORDGROUPOWNERCHANGE:
            return ObjectJSONUtils.extractFieldFromJSONString(s.getCurrentState(), "owner", "name");
        case StatusValue.Events.RECORDPROCESSINGCHANGE:
            return ObjectJSONUtils.extractFieldFromJSONString(s.getCurrentState(), "process");
        case StatusValue.Events.RECORDCATEGORYCHANGE:
            List<String> categories = ObjectJSONUtils.extractListOfFieldFromJSONString(s.getCurrentState(),
                    "category", "name");
            StringBuffer categoriesAsString = new StringBuffer("[ ");
            for (String categoryName : categories) {
                categoriesAsString.append(categoryName + " ");
            }
            categoriesAsString.append("]");
            return categoriesAsString.toString();
        case StatusValue.Events.RECORDVALIDATIONTRIGGERED:
            return s.getCurrentState().equals("1") ? "OK" : "KO";
        default:
            return "";
        }
    }

    private String extractPreviousStatus(MetadataStatus s) {
        switch (Integer.toString(s.getStatusValue().getId())) {
        case StatusValue.Events.ATTACHMENTDELETED:
            return s.getPreviousState();
        case StatusValue.Events.RECORDOWNERCHANGE:
            return ObjectJSONUtils.extractFieldFromJSONString(s.getPreviousState(), "owner", "name");
        case StatusValue.Events.RECORDGROUPOWNERCHANGE:
            return ObjectJSONUtils.extractFieldFromJSONString(s.getPreviousState(), "owner", "name");
        default:
            return "";
        }
    }

}