nu.yona.server.subscriptions.rest.BuddyController.java Source code

Java tutorial

Introduction

Here is the source code for nu.yona.server.subscriptions.rest.BuddyController.java

Source

/*******************************************************************************
 * Copyright (c) 2015, 2017 Stichting Yona Foundation This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
 *******************************************************************************/
package nu.yona.server.subscriptions.rest;

import static nu.yona.server.rest.Constants.PASSWORD_HEADER;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.ExposesResourceFor;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.hal.CurieProvider;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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.RequestHeader;
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.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

import nu.yona.server.analysis.rest.BuddyActivityController;
import nu.yona.server.crypto.seckey.CryptoSession;
import nu.yona.server.goals.rest.GoalController.GoalResourceAssembler;
import nu.yona.server.goals.service.GoalDto;
import nu.yona.server.goals.service.GoalServiceException;
import nu.yona.server.rest.ControllerBase;
import nu.yona.server.rest.JsonRootRelProvider;
import nu.yona.server.subscriptions.entities.BuddyAnonymized.Status;
import nu.yona.server.subscriptions.rest.BuddyController.BuddyResource;
import nu.yona.server.subscriptions.service.BuddyDto;
import nu.yona.server.subscriptions.service.BuddyService;
import nu.yona.server.subscriptions.service.BuddyServiceException;
import nu.yona.server.subscriptions.service.UserDto;
import nu.yona.server.subscriptions.service.UserService;
import nu.yona.server.util.TimeUtil;

@Controller
@ExposesResourceFor(BuddyResource.class)
@RequestMapping(value = "/users/{userId}/buddies", produces = { MediaType.APPLICATION_JSON_VALUE })
public class BuddyController extends ControllerBase {
    public static final String BUDDY_LINK = "buddy";

    @Autowired
    private BuddyService buddyService;

    @Autowired
    private UserService userService;

    @Autowired
    private CurieProvider curieProvider;

    /**
     * This method returns all the buddies that the given user has.
     * 
     * @param password The Yona password as passed on in the header of the request.
     * @param userId The ID of the user. This is part of the URL.
     * @return the list of buddies for the current user
     */
    @RequestMapping(value = "/", method = RequestMethod.GET)
    @ResponseBody
    public HttpEntity<Resources<BuddyResource>> getAllBuddies(
            @RequestHeader(value = PASSWORD_HEADER) Optional<String> password, @PathVariable UUID userId) {
        try (CryptoSession cryptoSession = CryptoSession.start(password,
                () -> userService.doPreparationsAndCheckCanAccessPrivateData(userId))) {
            return new ResponseEntity<>(createAllBuddiesCollectionResource(curieProvider, userId,
                    buddyService.getBuddiesOfUser(userId)), HttpStatus.OK);
        }
    }

    @RequestMapping(value = "/{buddyId}", method = RequestMethod.GET)
    @ResponseBody
    public HttpEntity<BuddyResource> getBuddy(@RequestHeader(value = PASSWORD_HEADER) Optional<String> password,
            @PathVariable UUID userId, @PathVariable UUID buddyId) {
        try (CryptoSession cryptoSession = CryptoSession.start(password,
                () -> userService.doPreparationsAndCheckCanAccessPrivateData(userId))) {

            return createOkResponse(buddyService.getBuddy(buddyId), createResourceAssembler(userId));
        }
    }

    @RequestMapping(value = "/", method = RequestMethod.POST)
    @ResponseBody
    public HttpEntity<BuddyResource> addBuddy(@RequestHeader(value = PASSWORD_HEADER) Optional<String> password,
            @PathVariable UUID userId, @RequestBody PostPutBuddyDto postPutBuddy) {
        try (CryptoSession cryptoSession = CryptoSession.start(password,
                () -> userService.doPreparationsAndCheckCanAccessPrivateData(userId))) {
            return createResponse(
                    buddyService.addBuddyToRequestingUser(userId, convertToBuddy(postPutBuddy), this::getInviteUrl),
                    HttpStatus.CREATED, createResourceAssembler(userId));
        }
    }

    @RequestMapping(value = "/{buddyId}", method = RequestMethod.DELETE)
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    public void removeBuddy(@RequestHeader(value = PASSWORD_HEADER) Optional<String> password,
            @PathVariable UUID userId, @PathVariable UUID buddyId,
            @RequestParam(value = "message", required = false) String messageStr) {
        try (CryptoSession cryptoSession = CryptoSession.start(password,
                () -> userService.doPreparationsAndCheckCanAccessPrivateData(userId))) {
            buddyService.removeBuddy(userId, buddyId, Optional.ofNullable(messageStr));
        }
    }

    @RequestMapping(value = "/{buddyId}/goals/{goalId}", method = RequestMethod.GET)
    @ResponseBody
    public HttpEntity<GoalDto> getGoal(@RequestHeader(value = PASSWORD_HEADER) Optional<String> password,
            @PathVariable UUID userId, @PathVariable UUID buddyId, @PathVariable UUID goalId) {
        try (CryptoSession cryptoSession = CryptoSession.start(password,
                () -> userService.doPreparationsAndCheckCanAccessPrivateData(userId))) {
            return createOkResponse(getGoal(userId, buddyId, goalId), new GoalResourceAssembler(userId));
        }
    }

    @RequestMapping(value = "/{buddyId}/goals/", method = RequestMethod.GET)
    @ResponseBody
    public HttpEntity<Resources<GoalDto>> getAllGoals(
            @RequestHeader(value = PASSWORD_HEADER) Optional<String> password, @PathVariable UUID userId,
            @PathVariable UUID buddyId) {
        try (CryptoSession cryptoSession = CryptoSession.start(password,
                () -> userService.doPreparationsAndCheckCanAccessPrivateData(userId))) {
            return new ResponseEntity<>(createAllGoalsCollectionResource(userId, buddyId, getGoals(buddyId)),
                    HttpStatus.OK);
        }
    }

    private GoalDto getGoal(UUID userId, UUID buddyId, UUID goalId) {
        BuddyDto buddy = buddyService.getBuddy(buddyId);
        Optional<GoalDto> goal = buddy.getGoals().orElse(Collections.emptySet()).stream()
                .filter(g -> g.getGoalId().equals(goalId)).findAny();
        return goal.orElseThrow(() -> GoalServiceException.goalNotFoundByIdForBuddy(userId, buddyId, goalId));
    }

    private Set<GoalDto> getGoals(UUID buddyId) {
        return buddyService.getBuddy(buddyId).getGoals().orElse(Collections.emptySet());
    }

    private BuddyDto convertToBuddy(PostPutBuddyDto postPutBuddy) {
        String userRelName = curieProvider.getNamespacedRelFor(BuddyDto.USER_REL_NAME);
        UserDto user = postPutBuddy.userInMap.get(userRelName);
        if (user == null) {
            throw BuddyServiceException.missingUser(userRelName);
        }
        return new BuddyDto(user, postPutBuddy.message, postPutBuddy.sendingStatus, postPutBuddy.receivingStatus,
                TimeUtil.utcNow());
    }

    public static Resources<BuddyResource> createAllBuddiesCollectionResource(CurieProvider curieProvider,
            UUID userId, Set<BuddyDto> allBuddiesOfUser) {
        return new Resources<>(new BuddyResourceAssembler(curieProvider, userId).toResources(allBuddiesOfUser),
                getAllBuddiesLinkBuilder(userId).withSelfRel());
    }

    static ControllerLinkBuilder getAllBuddiesLinkBuilder(UUID userId) {
        BuddyController methodOn = methodOn(BuddyController.class);
        return linkTo(methodOn.getAllBuddies(null, userId));
    }

    public String getInviteUrl(UUID newUserId, String tempPassword) {
        // getUserSelfLinkWithTempPassword should actually call expand, so we don't need to strip template parameters
        // This is not being done because of https://github.com/spring-projects/spring-hateoas/issues/703
        return stripTemplateParameters(UserController.getUserSelfLinkWithTempPassword(newUserId, tempPassword));
    }

    private String stripTemplateParameters(Link link) {
        String linkString = link.getHref();
        if (link.isTemplated()) {
            return linkString.substring(0, linkString.indexOf('{'));
        }
        return linkString;
    }

    private BuddyResourceAssembler createResourceAssembler(UUID userId) {
        return new BuddyResourceAssembler(curieProvider, userId);
    }

    public static Resources<GoalDto> createAllGoalsCollectionResource(UUID userId, UUID buddyId,
            Set<GoalDto> allGoalsOfUser) {
        return new Resources<>(
                new GoalResourceAssembler(true, goalId -> getGoalLinkBuilder(userId, buddyId, goalId))
                        .toResources(allGoalsOfUser),
                getAllGoalsLinkBuilder(userId, buddyId).withSelfRel());
    }

    public static ControllerLinkBuilder getBuddyLinkBuilder(UUID userId, UUID buddyId) {
        BuddyController methodOn = methodOn(BuddyController.class);
        return linkTo(methodOn.getBuddy(Optional.empty(), userId, buddyId));
    }

    public static ControllerLinkBuilder getGoalLinkBuilder(UUID userId, UUID buddyId, UUID goalId) {
        BuddyController methodOn = methodOn(BuddyController.class);
        return linkTo(methodOn.getGoal(Optional.empty(), userId, buddyId, goalId));
    }

    public static ControllerLinkBuilder getAllGoalsLinkBuilder(UUID userId, UUID buddyId) {
        BuddyController methodOn = methodOn(BuddyController.class);
        return linkTo(methodOn.getAllGoals(Optional.empty(), userId, buddyId));
    }

    static class PostPutBuddyDto {
        private final Map<String, UserDto> userInMap;
        private final String message;
        private final Status sendingStatus;
        private final Status receivingStatus;

        @JsonCreator
        public PostPutBuddyDto(@JsonProperty(value = "_embedded", required = true) Map<String, UserDto> userInMap,
                @JsonProperty("message") String message,
                @JsonProperty(value = "sendingStatus", required = true) Status sendingStatus,
                @JsonProperty(value = "receivingStatus", required = true) Status receivingStatus) {
            this.userInMap = userInMap;
            this.message = message;
            this.sendingStatus = sendingStatus;
            this.receivingStatus = receivingStatus;
        }
    }

    static class BuddyResource extends Resource<BuddyDto> {
        private final CurieProvider curieProvider;
        private final UUID userId;

        public BuddyResource(CurieProvider curieProvider, UUID userId, BuddyDto buddy) {
            super(buddy);
            this.curieProvider = curieProvider;
            this.userId = userId;
        }

        @JsonProperty("_embedded")
        public Map<String, Object> getEmbeddedResources() {
            if (getContent().getUser() == null) {
                return Collections.emptyMap();
            }

            HashMap<String, Object> result = new HashMap<>();
            result.put(curieProvider.getNamespacedRelFor(BuddyDto.USER_REL_NAME),
                    UserController.UserResourceAssembler.createInstanceForBuddy(curieProvider, userId)
                            .toResource(getContent().getUser()));

            Optional<Set<GoalDto>> goals = getContent().getGoals();
            goals.ifPresent(g -> result.put(curieProvider.getNamespacedRelFor(BuddyDto.GOALS_REL_NAME),
                    BuddyController.createAllGoalsCollectionResource(userId, getContent().getId(), g)));
            return result;
        }
    }

    static class BuddyResourceAssembler extends ResourceAssemblerSupport<BuddyDto, BuddyResource> {
        private final UUID userId;
        private final CurieProvider curieProvider;

        public BuddyResourceAssembler(CurieProvider curieProvider, UUID userId) {
            super(BuddyController.class, BuddyResource.class);
            this.curieProvider = curieProvider;
            this.userId = userId;
        }

        @Override
        public BuddyResource toResource(BuddyDto buddy) {
            BuddyResource buddyResource = instantiateResource(buddy);
            ControllerLinkBuilder selfLinkBuilder = getSelfLinkBuilder(buddy.getId());
            addSelfLink(selfLinkBuilder, buddyResource);
            addEditLink(selfLinkBuilder, buddyResource);
            if (buddy.getSendingStatus() == Status.ACCEPTED) {
                addDayActivityOverviewsLink(buddyResource);
                addWeekActivityOverviewsLink(buddyResource);
            }
            return buddyResource;
        }

        @Override
        protected BuddyResource instantiateResource(BuddyDto buddy) {
            return new BuddyResource(curieProvider, userId, buddy);
        }

        private ControllerLinkBuilder getSelfLinkBuilder(UUID buddyId) {
            return getBuddyLinkBuilder(userId, buddyId);
        }

        private void addSelfLink(ControllerLinkBuilder selfLinkBuilder, BuddyResource buddyResource) {
            buddyResource.add(selfLinkBuilder.withSelfRel());
        }

        private void addEditLink(ControllerLinkBuilder selfLinkBuilder, BuddyResource buddyResource) {
            buddyResource.add(selfLinkBuilder.withRel(JsonRootRelProvider.EDIT_REL));
        }

        private void addWeekActivityOverviewsLink(BuddyResource buddyResource) {
            buddyResource.add(BuddyActivityController
                    .getBuddyWeekActivityOverviewsLinkBuilder(userId, buddyResource.getContent().getId())
                    .withRel(BuddyActivityController.WEEK_OVERVIEW_LINK));
        }

        private void addDayActivityOverviewsLink(BuddyResource buddyResource) {
            buddyResource.add(BuddyActivityController
                    .getBuddyDayActivityOverviewsLinkBuilder(userId, buddyResource.getContent().getId())
                    .withRel(BuddyActivityController.DAY_OVERVIEW_LINK));
        }
    }
}