All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.ingogriebsch.spring.hateoas.siren.SirenLinkConverter Maven / Gradle / Ivy

/*-
 * Copyright 2019-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.ingogriebsch.spring.hateoas.siren;

import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import static java.util.stream.StreamSupport.stream;

import static com.google.common.collect.Lists.newArrayList;
import static de.ingogriebsch.spring.hateoas.siren.MediaTypes.SIREN_JSON;
import static org.springframework.hateoas.mediatype.html.HtmlInputType.TEXT_VALUE;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;

import java.util.List;

import de.ingogriebsch.spring.hateoas.siren.SirenAction.Field;
import lombok.RequiredArgsConstructor;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.hateoas.Affordance;
import org.springframework.hateoas.AffordanceModel.InputPayloadMetadata;
import org.springframework.hateoas.AffordanceModel.PropertyMetadata;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.mediatype.MessageResolver;
import org.springframework.hateoas.mediatype.html.HtmlInputType;
import org.springframework.http.MediaType;

/**
 * Converter which is able to either convert {@link Link}s (and their {@link Affordance}s) into a {@link SirenNavigables}, or the
 * other way around.
 * 
 * @author Ingo Griebsch
 * @since 1.0.0
 * @see SirenLink
 */
@RequiredArgsConstructor
class SirenLinkConverter {

    private final MessageResolver messageResolver;
    private final SirenActionFieldTypeConverter sirenActionFieldTypeConverter;

    SirenNavigables to(Iterable links) {
        return SirenNavigables.merge(stream(links.spliterator(), false).map(this::convert).collect(toList()));
    }

    List from(SirenNavigables navigables) {
        return slice(navigables).stream().map(this::convert).collect(toList());
    }

    SirenNavigables convert(Link link) {
        return SirenNavigables.of(links(link), actions(link));
    }

    private Link convert(SirenNavigables navigables) {
        SirenLink link = navigables.getLinks().iterator().next();
        String rel = link.getRels().stream().findAny()
            .orElseThrow(() -> new IllegalArgumentException(format("No rel available for link '%s'!", link)));

        return Link.of(link.getHref(), rel) //
            .withTitle(link.getTitle()) //
            .withType(link.getType());
    }

    private List links(Link link) {
        SirenLink sirenLink = SirenLink.builder() //
            .rel(link.getRel().value()) //
            .href(link.getHref()) //
            .title(title(link)) //
            .type(link.getType()) //
            .build();

        return newArrayList(sirenLink);
    }

    private List actions(Link link) {
        List result = newArrayList();
        for (SirenAffordanceModel model : affordanceModels(link)) {
            if (!GET.equals(model.getHttpMethod())) {
                result.add(action(model));
            }
        }
        return result;
    }

    private SirenAction action(SirenAffordanceModel model) {
        MediaType type = actionType(model, fieldsAvailable(model));
        List fields = fields(model, type);

        return SirenAction.builder() //
            .name(model.getName()) //
            .method(model.getHttpMethod()) //
            .href(model.getLink().getHref()) //
            .title(actionTitle(model.getName())) //
            .type(type != null ? type.toString() : null) //
            .fields(fields) //
            .build();
    }

    private List fields(SirenAffordanceModel model, MediaType actionType) {
        InputPayloadMetadata input = model.getInput();
        if (input == null) {
            return newArrayList();
        }
        return input.stream().map(pm -> field(pm, actionType)).collect(toList());
    }

    private Field field(PropertyMetadata propertyMetadata, MediaType actionType) {
        return Field.builder() //
            .name(propertyMetadata.getName()) //
            .type(fieldType(propertyMetadata, actionType))//
            .title(fieldTitle(propertyMetadata.getName())) //
            .build();
    }

    private String title(Link link) {
        String title = link.getTitle();
        if (title != null) {
            return title;
        }

        LinkRelation rel = link.getRel();
        return title(SirenLink.TitleResolvable.of(rel));
    }

    private String actionTitle(String name) {
        return name != null ? title(SirenAction.TitleResolvable.of(name)) : null;
    }

    private String fieldTitle(String name) {
        return name != null ? title(SirenAction.Field.TitleResolvable.of(name)) : null;
    }

    private String title(MessageSourceResolvable resolvable) {
        return messageResolver.resolve(resolvable);
    }

    private String fieldType(PropertyMetadata propertyMetadata, MediaType actionType) {
        return sirenActionFieldTypeConverter.execute(propertyMetadata, actionType) //
            .map(HtmlInputType::toString) //
            .orElse(TEXT_VALUE);
    }

    private static boolean fieldsAvailable(SirenAffordanceModel model) {
        return model.getInput().stream().count() > 0;
    }

    private static MediaType actionType(SirenAffordanceModel model, boolean fieldsAvailable) {
        MediaType fallback = fieldsAvailable ? APPLICATION_FORM_URLENCODED : null;
        MediaType mediaType = model.getInput().getPrimaryMediaType();
        return mediaType != null && fieldsAvailable ? mediaType : fallback;
    }

    private static List slice(SirenNavigables navigables) {
        return navigables.getLinks().stream().map(l -> slice(l, navigables.getActions())).collect(toList());
    }

    private static SirenNavigables slice(SirenLink link, List actions) {
        return SirenNavigables.of(newArrayList(link), actions);
    }

    private static List affordanceModels(Link link) {
        return link.getAffordances().stream().map(a -> a.getAffordanceModel(SIREN_JSON)).map(SirenAffordanceModel.class::cast)
            .collect(toList());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy