org.fao.geonet.api.groups.GroupsApi.java Source code

Java tutorial

Introduction

Here is the source code for org.fao.geonet.api.groups.GroupsApi.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.groups;

import com.google.common.base.Functions;
import com.google.common.collect.Lists;
import io.swagger.annotations.*;
import jeeves.server.UserSession;
import jeeves.server.context.ServiceContext;
import org.apache.commons.io.FileUtils;
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.NotAllowedException;
import org.fao.geonet.api.exception.ResourceNotFoundException;
import org.fao.geonet.api.records.attachments.AttachmentsApi;
import org.fao.geonet.api.tools.i18n.LanguageUtils;
import org.fao.geonet.constants.Geonet;
import org.fao.geonet.domain.*;
import org.fao.geonet.kernel.DataManager;
import org.fao.geonet.repository.*;
import org.fao.geonet.repository.specification.GroupSpecs;
import org.fao.geonet.repository.specification.OperationAllowedSpecs;
import org.fao.geonet.repository.specification.UserGroupSpecs;
import org.fao.geonet.resources.Resources;
import org.fao.geonet.utils.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specifications;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.WebRequest;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.sql.SQLException;
import java.util.*;

import static org.springframework.data.jpa.domain.Specifications.where;

@RequestMapping(value = { "/api/groups", "/api/" + API.VERSION_0_1 + "/groups" })
@Api(value = "groups", tags = "groups", description = "Groups operations")
@Controller("groups")
public class GroupsApi {
    /**
     * API logo note.
     */
    private static final String API_GET_LOGO_NOTE = "If last-modified header "
            + "is present it is used to check if the logo has been modified since "
            + "the header date. If it hasn't been modified returns an empty 304 Not"
            + " Modified response. If modified returns the image. If the group has "
            + "no logo then returns a transparent 1x1 px PNG image.";

    /**
     * Logger name.
     */
    public static final String LOGGER = Geonet.GEONETWORK + ".api.groups";
    /**
     * Six hours in seconds.
     */
    private static final int SIX_HOURS = 60 * 60 * 6;
    /**
     * Transparent 1x1 px PNG encoded in Base64.
     */
    private static final String TRANSPARENT_1_X_1_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR"
            + "42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==";
    /**
     * Transparent 1x1 px PNG.
     */
    private static final byte[] TRANSPARENT_1_X_1_PNG = org.apache.commons.codec.binary.Base64
            .decodeBase64(TRANSPARENT_1_X_1_PNG_BASE64);

    public static final String API_PARAM_GROUP_DETAILS = "Group details";
    public static final String API_PARAM_GROUP_IDENTIFIER = "Group identifier";
    public static final String MSG_GROUP_WITH_IDENTIFIER_NOT_FOUND = "Group with identifier '%d' not found";

    /**
     * Message source.
     */
    @Autowired
    @Qualifier("apiMessages")
    private ResourceBundleMessageSource messages;

    /**
     * Language utils used to detect the requested language.
     */
    @Autowired
    private LanguageUtils languageUtils;

    /**
     * Writes the group logo image to the response. If no image is found it
     * writes a 1x1 transparent PNG. If the request contain cache related
     * headers it checks if the resource has changed and return a 304 Not
     * Modified response if not changed.
     *
     * @param groupId    the group identifier.
     * @param webRequest the web request.
     * @param request    the native HTTP Request.
     * @param response   the servlet response.
     * @throws ResourceNotFoundException if no group exists with groupId.
     */
    @ApiOperation(value = "Get the group logo image.", nickname = "get", notes = API_GET_LOGO_NOTE)
    @RequestMapping(value = "/{groupId}/logo", method = RequestMethod.GET)
    public void getGroupLogo(
            @ApiParam(value = "Group identifier", required = true) @PathVariable(value = "groupId") final Integer groupId,
            @ApiIgnore final WebRequest webRequest, HttpServletRequest request, HttpServletResponse response)
            throws ResourceNotFoundException {

        Locale locale = languageUtils.parseAcceptLanguage(request.getLocales());

        ApplicationContext context = ApplicationContextHolder.get();
        ServiceContext serviceContext = ApiUtils.createServiceContext(request, locale.getISO3Country());
        if (context == null) {
            throw new RuntimeException("ServiceContext not available");
        }

        GroupRepository groupRepository = context.getBean(GroupRepository.class);

        Group group = groupRepository.findOne(groupId);
        if (group == null) {
            throw new ResourceNotFoundException(
                    messages.getMessage("api.groups.group_not_found", new Object[] { groupId }, locale));
        }
        try {
            final Path logosDir = Resources.locateLogosDir(serviceContext);
            final Path harvesterLogosDir = Resources.locateHarvesterLogosDir(serviceContext);
            final String logoUUID = group.getLogo();
            Path imagePath = null;
            FileTime lastModifiedTime = null;
            if (StringUtils.isNotBlank(logoUUID) && !logoUUID.startsWith("http://")
                    && !logoUUID.startsWith("https//")) {
                imagePath = Resources.findImagePath(logoUUID, logosDir);
                if (imagePath == null) {
                    imagePath = Resources.findImagePath(logoUUID, harvesterLogosDir);
                }
                if (imagePath != null) {
                    lastModifiedTime = Files.getLastModifiedTime(imagePath);
                    if (webRequest.checkNotModified(lastModifiedTime.toMillis())) {
                        // webRequest.checkNotModified sets the right HTTP headers
                        response.setDateHeader("Expires", System.currentTimeMillis() + SIX_HOURS * 1000L);

                        return;
                    }
                    response.setContentType(AttachmentsApi.getFileContentType(imagePath));
                    response.setContentLength((int) Files.size(imagePath));
                    response.addHeader("Cache-Control", "max-age=" + SIX_HOURS + ", public");
                    response.setDateHeader("Expires", System.currentTimeMillis() + SIX_HOURS * 1000L);
                    FileUtils.copyFile(imagePath.toFile(), response.getOutputStream());
                }
            }

            if (imagePath == null) {
                // no logo image found. Return a transparent 1x1 png
                lastModifiedTime = FileTime.fromMillis(0);
                if (webRequest.checkNotModified(lastModifiedTime.toMillis())) {
                    return;
                }
                response.setContentType("image/png");
                response.setContentLength(TRANSPARENT_1_X_1_PNG.length);
                response.addHeader("Cache-Control", "max-age=" + SIX_HOURS + ", public");
                response.getOutputStream().write(TRANSPARENT_1_X_1_PNG);
            }

        } catch (IOException e) {
            Log.error(LOGGER,
                    String.format("There was an error accessing the logo of the group with id '%d'", groupId));
            throw new RuntimeException(e);
        }
    }

    @ApiOperation(value = "Get groups", notes = "The catalog contains one or more groups. By default, there is 3 reserved groups "
            + "(Internet, Intranet, Guest) and a sample group.<br/>"
            + "This service returns all catalog groups when not authenticated or "
            + "when current is user is an administrator. The list can contains or not "
            + "reserved groups depending on the parameters.<br/>" + "When authenticated, return user groups "
            + "optionally filtered on a specific user profile.", nickname = "getGroups")
    @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    public List<Group> getGroups(
            @ApiParam(value = "Including Internet, Intranet, Guest groups or not") @RequestParam(required = false, defaultValue = "false") boolean withReservedGroup,
            @ApiParam(value = "For a specific profile") @RequestParam(required = false) String profile,
            @ApiIgnore HttpSession httpSession) throws Exception {
        UserSession session = ApiUtils.getUserSession(httpSession);

        if (!session.isAuthenticated() || profile == null) {
            return getGroups(session, null, withReservedGroup, !withReservedGroup);
        } else {
            return getGroups(session, Profile.findProfileIgnoreCase(profile), false, false);
        }
    }

    @ApiOperation(value = "Add a group", notes = "Return the identifier of the group created.", authorizations = {
            @Authorization(value = "basicAuth") }, nickname = "addGroup")
    @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.PUT)
    @ResponseStatus(value = HttpStatus.OK)
    @PreAuthorize("hasRole('UserAdmin')")
    @ApiResponses(value = { @ApiResponse(code = 201, message = "Group created."),
            @ApiResponse(code = 400, message = "Group with that id or name already exist."),
            @ApiResponse(code = 403, message = ApiParams.API_RESPONSE_NOT_ALLOWED_ONLY_USER_ADMIN) })
    @ResponseBody
    public ResponseEntity<Integer> addGroup(@ApiParam(value = API_PARAM_GROUP_DETAILS) @RequestBody Group group)
            throws Exception {
        ApplicationContext appContext = ApplicationContextHolder.get();
        GroupRepository groupRepository = appContext.getBean(GroupRepository.class);

        final Group existingId = groupRepository.findOne(group.getId());

        if (existingId != null) {
            throw new IllegalArgumentException(String.format("A group with id '%d' already exist.", group.getId()));
        }

        final Group existingName = groupRepository.findByName(group.getName());
        if (existingName != null) {
            throw new IllegalArgumentException(
                    String.format("A group with name '%s' already exist.", group.getName()));
        }

        // Populate languages if not already set
        LanguageRepository langRepository = appContext.getBean(LanguageRepository.class);
        java.util.List<Language> allLanguages = langRepository.findAll();
        Map<String, String> labelTranslations = group.getLabelTranslations();
        for (Language l : allLanguages) {
            String label = labelTranslations.get(l.getId());
            group.getLabelTranslations().put(l.getId(), label == null ? group.getName() : label);
        }
        group = groupRepository.save(group);

        return new ResponseEntity<>(group.getId(), HttpStatus.CREATED);
    }

    @ApiOperation(value = "Get group", notes = "Return the requested group details.", nickname = "getGroup")
    @RequestMapping(value = "/{groupIdentifier}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
    @ResponseStatus(value = HttpStatus.OK)
    @ApiResponses(value = { @ApiResponse(code = 404, message = ApiParams.API_RESPONSE_RESOURCE_NOT_FOUND) })
    @ResponseBody
    public Group getGroup(@ApiParam(value = API_PARAM_GROUP_IDENTIFIER) @PathVariable Integer groupIdentifier)
            throws Exception {
        final GroupRepository groupRepository = ApplicationContextHolder.get().getBean(GroupRepository.class);
        final Group group = groupRepository.findOne(groupIdentifier);

        if (group == null) {
            throw new ResourceNotFoundException(
                    String.format(MSG_GROUP_WITH_IDENTIFIER_NOT_FOUND, groupIdentifier));
        }
        return group;
    }

    @ApiOperation(value = "Get group users", notes = "", authorizations = {
            @Authorization(value = "basicAuth") }, nickname = "getGroupUsers")
    @RequestMapping(value = "/{groupIdentifier}/users", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
    @ResponseStatus(value = HttpStatus.OK)
    @PreAuthorize("hasRole('UserAdmin')")
    @ApiResponses(value = { @ApiResponse(code = 200, message = "List of users in that group."),
            @ApiResponse(code = 404, message = ApiParams.API_RESPONSE_RESOURCE_NOT_FOUND),
            @ApiResponse(code = 403, message = ApiParams.API_RESPONSE_NOT_ALLOWED_ONLY_USER_ADMIN) })
    @ResponseBody
    public List<User> getGroupUsers(
            @ApiParam(value = API_PARAM_GROUP_IDENTIFIER) @PathVariable Integer groupIdentifier) throws Exception {
        ApplicationContext applicationContext = ApplicationContextHolder.get();
        GroupRepository groupRepository = applicationContext.getBean(GroupRepository.class);
        final Group group = groupRepository.findOne(groupIdentifier);

        if (group == null) {
            throw new ResourceNotFoundException(
                    String.format(MSG_GROUP_WITH_IDENTIFIER_NOT_FOUND, groupIdentifier));
        }
        UserRepository userRepository = applicationContext.getBean(UserRepository.class);
        return userRepository.findAllUsersInUserGroups(UserGroupSpecs.hasGroupId(groupIdentifier));
    }

    @ApiOperation(value = "Update a group", notes = "", authorizations = {
            @Authorization(value = "basicAuth") }, nickname = "updateGroup")
    @RequestMapping(value = "/{groupIdentifier}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.PUT)
    @ResponseStatus(value = HttpStatus.NO_CONTENT)
    @PreAuthorize("hasRole('UserAdmin')")
    @ApiResponses(value = { @ApiResponse(code = 204, message = "Group updated."),
            @ApiResponse(code = 404, message = ApiParams.API_RESPONSE_RESOURCE_NOT_FOUND),
            @ApiResponse(code = 403, message = ApiParams.API_RESPONSE_NOT_ALLOWED_ONLY_USER_ADMIN) })
    @ResponseBody
    public void updateGroup(@ApiParam(value = API_PARAM_GROUP_IDENTIFIER) @PathVariable Integer groupIdentifier,
            @ApiParam(value = API_PARAM_GROUP_DETAILS) @RequestBody Group group) throws Exception {
        GroupRepository groupRepository = ApplicationContextHolder.get().getBean(GroupRepository.class);

        final Group existing = groupRepository.findOne(groupIdentifier);
        if (existing == null) {
            throw new ResourceNotFoundException(
                    String.format(MSG_GROUP_WITH_IDENTIFIER_NOT_FOUND, groupIdentifier));
        } else {
            groupRepository.save(group);
        }
    }

    @ApiOperation(value = "Remove a group", notes = "Remove a group by first removing sharing settings, link to users and "
            + "finally reindex all affected records.", authorizations = {
                    @Authorization(value = "basicAuth") }, nickname = "deleteGroup")
    @RequestMapping(value = "/{groupIdentifier}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.DELETE)
    @ResponseStatus(value = HttpStatus.NO_CONTENT)
    @PreAuthorize("hasRole('Administrator')")
    @ApiResponses(value = { @ApiResponse(code = 204, message = "Group removed."),
            @ApiResponse(code = 404, message = ApiParams.API_RESPONSE_RESOURCE_NOT_FOUND),
            @ApiResponse(code = 403, message = ApiParams.API_RESPONSE_NOT_ALLOWED_ONLY_USER_ADMIN) })
    @ResponseBody
    public void deleteGroup(@ApiParam(value = "Group identifier.") @PathVariable Integer groupIdentifier,
            @ApiParam(value = "Force removal even if records are assigned to that group.") @RequestParam(defaultValue = "false") boolean force,
            @ApiIgnore ServletRequest request) throws Exception {
        GroupRepository groupRepository = ApplicationContextHolder.get().getBean(GroupRepository.class);

        Group group = groupRepository.findOne(groupIdentifier);

        if (group != null) {
            OperationAllowedRepository operationAllowedRepo = ApplicationContextHolder.get()
                    .getBean(OperationAllowedRepository.class);
            UserGroupRepository userGroupRepo = ApplicationContextHolder.get().getBean(UserGroupRepository.class);

            List<Integer> reindex = operationAllowedRepo
                    .findAllIds(OperationAllowedSpecs.hasGroupId(groupIdentifier), OperationAllowedId_.metadataId);

            if (reindex.size() > 0 && force) {
                operationAllowedRepo.deleteAllByIdAttribute(OperationAllowedId_.groupId, groupIdentifier);

                //--- reindex affected metadata
                DataManager dm = ApplicationContextHolder.get().getBean(DataManager.class);
                dm.indexMetadata(Lists.transform(reindex, Functions.toStringFunction()));
            } else if (reindex.size() > 0 && !force) {
                throw new NotAllowedException(String.format(
                        "Group %s has privileges associated with %d record(s). Add 'force' parameter to remove it or remove privileges associated with that group first.",
                        group.getName(), reindex.size()));
            }

            final List<Integer> users = userGroupRepo.findUserIds(where(UserGroupSpecs.hasGroupId(group.getId())));
            if (users.size() > 0 && force) {
                userGroupRepo.deleteAllByIdAttribute(UserGroupId_.groupId, Arrays.asList(groupIdentifier));
            } else if (users.size() > 0 && !force) {
                throw new NotAllowedException(String.format(
                        "Group %s is associated with %d user(s). Add 'force' parameter to remove it or remove users associated with that group first.",
                        group.getName(), users.size()));
            }

            groupRepository.delete(groupIdentifier);

        } else {
            throw new ResourceNotFoundException(
                    String.format(MSG_GROUP_WITH_IDENTIFIER_NOT_FOUND, groupIdentifier));
        }
    }

    /**
     * Retrieves a user's groups.
     *
     * @param includingSystemGroups if true, also returns the system groups ('GUEST', 'intranet',
     *                              'all')
     * @param all                   if true returns all the groups, even those the user doesn't
     *                              belongs to
     */
    private List<Group> getGroups(UserSession session, Profile profile, boolean includingSystemGroups, boolean all)
            throws SQLException {
        ApplicationContext applicationContext = ApplicationContextHolder.get();
        final GroupRepository groupRepository = applicationContext.getBean(GroupRepository.class);
        final UserGroupRepository userGroupRepository = applicationContext.getBean(UserGroupRepository.class);
        final Sort sort = SortUtils.createSort(Group_.id);

        if (all || !session.isAuthenticated() || Profile.Administrator == session.getProfile()) {
            if (includingSystemGroups) {
                return groupRepository.findAll(null, sort);
            } else {
                return groupRepository.findAll(Specifications.not(GroupSpecs.isReserved()), sort);
            }
        } else {
            Specifications<UserGroup> spec = Specifications
                    .where(UserGroupSpecs.hasUserId(session.getUserIdAsInt()));
            // you're no Administrator
            // retrieve your groups
            if (profile != null) {
                spec = spec.and(UserGroupSpecs.hasProfile(profile));
            }
            Set<Integer> ids = new HashSet<Integer>(userGroupRepository.findGroupIds(spec));

            // include system groups if requested (used in harvesters)
            if (includingSystemGroups) {
                // these DB keys of system groups are hardcoded !
                for (ReservedGroup reservedGroup : ReservedGroup.values()) {
                    ids.add(reservedGroup.getId());
                }
            }

            // retrieve all groups and filter to only user one
            List<Group> groups = groupRepository.findAll(null, sort);
            groups.removeIf(g -> !ids.contains(g.getId()));
            return groups;
        }
    }
}