public static Link createVerifyPasswordLink(UserClient user) { Link verifyPasswordLink = linkTo(methodOn(UserClientsResource.class).verifyPassword(null)).withRel("verifyPassword"); UriTemplate verifyPasswordUriTemplate = new UriTemplate(verifyPasswordLink.getHref()) .with( new TemplateVariables( new TemplateVariable("password", TemplateVariable.VariableType.REQUEST_PARAM))); return new Link(verifyPasswordUriTemplate, verifyPasswordLink.getRel()); }
@Test public void usesCustomLinkProvided() { Link link = new Link("http://foo:9090", "rel"); PagedResourcesAssembler<Person> assembler = new PagedResourcesAssembler<Person>(resolver, null); PagedResources<Resource<Person>> resources = assembler.toResource(createPage(1), link); assertThat(resources.getLink(Link.REL_PREVIOUS).getHref(), startsWith(link.getHref())); assertThat(resources.getLink(Link.REL_NEXT).getHref(), startsWith(link.getHref())); }
// Creates a user resource @Override public UserResource toResource(User user) { UserResource userResource = new UserResource(); userResource.setUserId(user.getUserId()); userResource.setUserName(user.getUserName()); // HATEOAS link builder object Link link = linkTo(methodOn(UserController.class).getUser(user.getUserId())).withSelfRel(); userResource.add(link.withSelfRel()); return userResource; }
private UriTemplate createUriTemplate(String requestParameterName, Link linkName) { return new UriTemplate(linkName.getHref()) .with( new TemplateVariables( new TemplateVariable( requestParameterName, TemplateVariable.VariableType.REQUEST_PARAM))); }
@RequestMapping(method = RequestMethod.POST) ResponseEntity<?> add(@PathVariable String userId, @RequestBody CoinOrder input) { this.validateUser(userId); return this.accountRepository .findByUsername(userId) .map( account -> { CoinOrder result = coinOrderRepository.save( new CoinOrder(account, input.type, input.quantity, input.priceInCents)); Link forOneBookmark = new CoinOrderResource(result).getLink("self"); return ResponseEntity.created(URI.create(forOneBookmark.getHref())).build(); }) .orElse(ResponseEntity.noContent().build()); }
@Test public void accessServiceUsingRestTemplate() { // Access root resource URI uri = URI.create(String.format(SERVICE_URI, port)); RequestEntity<Void> request = RequestEntity.get(uri).accept(HAL_JSON).build(); Resource<Object> rootLinks = restOperations.exchange(request, new ResourceType<Object>() {}).getBody(); Links links = new Links(rootLinks.getLinks()); // Follow stores link Link storesLink = links.getLink("stores").expand(); request = RequestEntity.get(URI.create(storesLink.getHref())).accept(HAL_JSON).build(); Resources<Store> stores = restOperations.exchange(request, new ResourcesType<Store>() {}).getBody(); stores.getContent().forEach(store -> log.info("{} - {}", store.name, store.address)); }
/** * Creates the {@link Descriptor}s for pagination parameters. * * @param type * @return */ private List<Descriptor> getPaginationDescriptors(Class<?> type, HttpMethod method) { RepositoryInformation information = repositories.getRepositoryInformationFor(type); if (!information.isPagingRepository() || !getType(method).equals(Type.SAFE)) { return Collections.emptyList(); } Link linkToCollectionResource = entityLinks.linkToCollectionResource(type); List<TemplateVariable> variables = linkToCollectionResource.getVariables(); List<Descriptor> descriptors = new ArrayList<Descriptor>(variables.size()); ProjectionDefinitionConfiguration projectionConfiguration = configuration.projectionConfiguration(); for (TemplateVariable variable : variables) { // Skip projection parameter if (projectionConfiguration.getParameterName().equals(variable.getName())) { continue; } ResourceDescription description = SimpleResourceDescription.defaultFor(variable.getDescription()); descriptors.add( // descriptor() . // name(variable.getName()) . // type(Type.SEMANTIC) . // doc(getDocFor(description)) . // build()); } return descriptors; }
/* * (non-Javadoc) * @see org.springframework.hateoas.hal.CurieProvider#getNamespacedRelFrom(org.springframework.hateoas.Link) */ @Override public String getNamespacedRelFrom(Link link) { return getNamespacedRelFor(link.getRel()); }
@Override public UserClientResource toResource(UserClient user) { if (user == null) { return null; } Set<String> perms = new HashSet<>(); for (GrantedAuthority grantedAuthority : user.getAuthorities()) { // strip off the client and team specifics on the authorities Matcher matcher = specializedPermissions.matcher(grantedAuthority.getAuthority()); if (matcher.matches()) { perms.add(matcher.replaceAll("$1")); } else { perms.add(grantedAuthority.getAuthority()); } } if (user.isSuperUser()) { // give the super user all team permissions (not necessary but nice for the UI) user.setTeamRoles(superUserTeamRoleGenerator.allPermissionsOnAllTeams(user.getClient())); } // add the teams to which this user has access Set<TeamResource> teams = new HashSet<>(); for (UserClientUserClientTeamRole userClientUserClientTeamRole : user.getTeamRoles()) { teams.add(roleTeamResourceResourceAssembler.toResource(userClientUserClientTeamRole)); } boolean interruptFlow = false; if (user.getNotNowExpirationTime() != null) { LocalDateTime dateTime = LocalDateTime.now(DateTimeZone.UTC); interruptFlow = dateTime.isBefore(user.getNotNowExpirationTime()); } UserClientResource ret = new UserClientResource( user.getId(), user.getVersion(), user.getLogin(), user.getFirstName(), user.getLastName(), user.getEmail(), user.isActive(), user.isAccountNonExpired(), user.isAccountNonLocked(), user.isCredentialsNonExpired(), user.isEmailValidated(), user.isSecretQuestionCreated(), clientResourceAssembler.toResource(user.getClient()), new ArrayList<>(perms), user.isImpersonated(), user.getNotNowExpirationTime(), user.getPasswordExpireationDateTime(), user.getPasswordSavedDateTime(), interruptFlow); ret.setSecurityQuestionsNotRequiredForReset(user.isSecurityQuestionsNotRequiredForReset()); ret.add(linkTo(methodOn(UserClientsResource.class).authenticated()).withRel("authenticated")); if (!CollectionUtils.isEmpty(teams)) { ret.setTeams(teams); } if (!user.isImpersonated()) { // non-impersonation users only ret.add(linkTo(methodOn(UserClientsResource.class).getById(user.getId())).withSelfRel()); ret.add(updateUserClientLink(user)); ret.add(createVerifyPasswordLink(user)); Link link = linkTo( methodOn(UserClientSecretQuestionResponsesResource.class) .secretQuestionResponses(user.getId(), null, null, null)) .withRel("secretQuestionResponses"); ret.add(new Link(createUriTemplate("password", link), link.getRel())); Link updateUserClientSecretQuestion = linkTo(methodOn(UserClientsResource.class).updateUserClient(user.getId(), null)) .withRel("updateUserClientSecretQuestionFlag"); ret.add( new Link( createUriTemplate("secretQuestionsCreated", updateUserClientSecretQuestion), updateUserClientSecretQuestion.getRel())); ret.add( linkTo( methodOn(UserClientSecretQuestionResponsesResource.class) .secretQuestionAsteriskResponse(user.getId(), null, null)) .withRel("secretQuestionAsteriskResponses")); ret.add( linkTo(methodOn(UserClientsResource.class).sendValidationEmail(user.getId())) .withRel("sendValidationEmail")); ret.add( linkTo(methodOn(UserClientsPasswordResource.class).changePassword(null, null, null)) .withRel("changePassword")); ret.add(linkTo(methodOn(UserClientsResource.class).notNow(user.getId())).withRel("notNow")); } else { // impersonation users ret.add( new Link( UriComponentsBuilder.fromHttpUrl( linkTo(methodOn(UserClientsResource.class).getById(1l)) .withSelfRel() .getHref()) .replacePath(adminEntryPoint) .build(false) .toUriString(), "adminApp")); } return ret; }
@Override public void serialize(List<Link> links, JsonGenerator jgen, SerializerProvider serializerProvider) throws IOException { try { Collection<Link> simpleLinks = new ArrayList<Link>(); Collection<Affordance> affordances = new ArrayList<Affordance>(); Collection<Link> templatedLinks = new ArrayList<Link>(); Collection<Affordance> templatedAffordances = new ArrayList<Affordance>(); Collection<Affordance> collectionAffordances = new ArrayList<Affordance>(); Link selfRel = null; for (Link link : links) { if (link instanceof Affordance) { final Affordance affordance = (Affordance) link; final List<ActionDescriptor> actionDescriptors = affordance.getActionDescriptors(); if (!actionDescriptors.isEmpty()) { if (affordance.isTemplated()) { templatedAffordances.add(affordance); } else { if (!affordance.isSelfRel() && Cardinality.COLLECTION == affordance.getCardinality()) { collectionAffordances.add(affordance); } else { affordances.add(affordance); } } } else { if (affordance.isTemplated()) { templatedLinks.add(affordance); } else { simpleLinks.add(affordance); } } } else if (link.isTemplated()) { templatedLinks.add(link); } else { simpleLinks.add(link); } if ("self".equals(link.getRel())) { selfRel = link; } } for (Affordance templatedAffordance : templatedAffordances) { jgen.writeObjectFieldStart(templatedAffordance.getRel()); jgen.writeStringField("@type", "hydra:IriTemplate"); jgen.writeStringField("hydra:template", templatedAffordance.getHref()); final List<ActionDescriptor> actionDescriptors = templatedAffordance.getActionDescriptors(); ActionDescriptor actionDescriptor = actionDescriptors.get(0); jgen.writeArrayFieldStart("hydra:mapping"); writeHydraVariableMapping(jgen, actionDescriptor, actionDescriptor.getPathVariableNames()); writeHydraVariableMapping(jgen, actionDescriptor, actionDescriptor.getRequestParamNames()); jgen.writeEndArray(); jgen.writeEndObject(); } for (Link templatedLink : templatedLinks) { // we only have the template, no access to method params jgen.writeObjectFieldStart(templatedLink.getRel()); jgen.writeStringField("@type", "hydra:IriTemplate"); jgen.writeStringField("hydra:template", templatedLink.getHref()); jgen.writeArrayFieldStart("hydra:mapping"); writeHydraVariableMapping(jgen, null, templatedLink.getVariableNames()); jgen.writeEndArray(); jgen.writeEndObject(); } @SuppressWarnings("unchecked") Deque<LdContext> contextStack = (Deque<LdContext>) serializerProvider.getAttribute(JacksonHydraSerializer.KEY_LD_CONTEXT); String currentVocab = (contextStack != null && !contextStack.isEmpty()) ? contextStack.peek().vocab : null; // related collections if (!collectionAffordances.isEmpty()) { jgen.writeArrayFieldStart("hydra:collection"); for (Affordance collectionAffordance : collectionAffordances) { jgen.writeStartObject(); jgen.writeStringField(JsonLdKeywords.AT_TYPE, "hydra:Collection"); jgen.writeStringField(JsonLdKeywords.AT_ID, collectionAffordance.getHref()); jgen.writeObjectFieldStart("hydra:manages"); jgen.writeStringField("hydra:property", collectionAffordance.getRel()); // a) in the case of </Alice> :knows /bob the subject must be /Alice // b) in the case of <> orderedItem /latte-1 the subject is an anonymous resource of type // Order // c) in the case of </order/1> :seller </store> the object must be the store // 1. where is the information *about* the subject or object: the type or @id // 2. how to decide if the collection manages items for a subject or object? // ad 1.) // ad a) self rel of the Resource that has the collection affordance: looking through link // list // at serialization time is possible // ad b) a candidate is the class given as value of @ExposesResourceFor on the controller // that defines the method which leads us to believe we have a collection: // either a GET /orders handler having a collection return type or the POST /orders. // Works only if the GET or POST has no request mapping path of its own // an alternative is to use {} without type if @ExposesResourceFor is not present // ad c) like a // ad 2.) // we know the affordance is a collection // * we could pass information into build(rel), like // .rel().reverseRel("schema:seller").build() // * we could use build("myReversedTerm") and look at the @context to see if it is // reversed, // if so, we know that the manages block must use object not subject. However weird if // the // reverse term is never really used in the json-ld // * we could have a registry which says if a property is meant to be reversed in a // certain context // and use that to find out if we need subject or object, like :seller -> if (selfRel != null) { // prefer rev over rel, assuming that rev exists to be used by RDF serialization if (collectionAffordance.getRev() != null) { jgen.writeStringField("hydra:object", selfRel.getHref()); } else if (collectionAffordance.getRel() != null) { jgen.writeStringField("hydra:subject", selfRel.getHref()); } } else { // prefer rev over rel, assuming that rev exists to be used by RDF serialization if (collectionAffordance.getRev() != null) { jgen.writeObjectFieldStart("hydra:object"); jgen.writeEndObject(); } else if (collectionAffordance.getRel() != null) { jgen.writeObjectFieldStart("hydra:subject"); jgen.writeEndObject(); } } jgen.writeEndObject(); // end manages List<ActionDescriptor> actionDescriptors = collectionAffordance.getActionDescriptors(); if (!actionDescriptors.isEmpty()) { jgen.writeArrayFieldStart("hydra:operation"); } writeActionDescriptors(jgen, currentVocab, actionDescriptors); if (!actionDescriptors.isEmpty()) { jgen.writeEndArray(); // end hydra:operation } jgen.writeEndObject(); // end collection } jgen.writeEndArray(); } for (Affordance affordance : affordances) { final String rel = affordance.getRel(); List<ActionDescriptor> actionDescriptors = affordance.getActionDescriptors(); if (!actionDescriptors.isEmpty()) { if (!Link.REL_SELF.equals(rel)) { jgen.writeObjectFieldStart(rel); // begin rel } jgen.writeStringField(JsonLdKeywords.AT_ID, affordance.getHref()); jgen.writeArrayFieldStart("hydra:operation"); } writeActionDescriptors(jgen, currentVocab, actionDescriptors); if (!actionDescriptors.isEmpty()) { jgen.writeEndArray(); // end hydra:operation if (!Link.REL_SELF.equals(rel)) { jgen.writeEndObject(); // end rel } } } for (Link simpleLink : simpleLinks) { final String rel = simpleLink.getRel(); if (Link.REL_SELF.equals(rel)) { jgen.writeStringField("@id", simpleLink.getHref()); } else { String linkAttributeName = IanaRels.isIanaRel(rel) ? IANA_REL_PREFIX + rel : rel; jgen.writeObjectFieldStart(linkAttributeName); jgen.writeStringField("@id", simpleLink.getHref()); jgen.writeEndObject(); } } } catch (IntrospectionException e) { throw new RuntimeException(e); } }