/*
* Copyright 2015 Petr Bouda
*
* 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
*
* http://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 org.joyrest.routing;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.joyrest.interceptor.Interceptor;
import org.joyrest.logging.JoyLogger;
import org.joyrest.model.RoutePart;
import org.joyrest.model.http.HttpMethod;
import org.joyrest.model.http.MediaType;
import org.joyrest.model.request.ImmutableRequest;
import org.joyrest.model.request.InternalRequest;
import org.joyrest.model.response.InternalResponse;
import org.joyrest.routing.entity.Type;
import org.joyrest.routing.security.Role;
import org.joyrest.transform.Reader;
import org.joyrest.transform.Writer;
import org.joyrest.utils.CollectionUtils;
import static org.joyrest.model.http.MediaType.WILDCARD;
import static org.joyrest.utils.CollectionUtils.nonEmpty;
import static org.joyrest.utils.PathUtils.createPathParts;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
import static java.util.Objects.isNull;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
/**
* Container for all information about one route {@link InternalRoute}
*
* @author pbouda
*/
public class InternalRoute implements Route {
private final static JoyLogger logger = new JoyLogger(InternalRoute.class);
private final static String SLASH = "/";
private final HttpMethod httpMethod;
/* List of the all path's parts which contains this route */
private final List<RoutePart<?>> routeParts;
/* Map of the all path params which contains this route */
private final Map<String, RoutePart<?>> pathParams = new HashMap<>();
/* All Readers and Writers added to the application dedicated for this route */
private Map<MediaType, Reader> readers = new HashMap<>();
private Map<MediaType, Writer> writers = new HashMap<>();
/* It is not FINAL because of adding a controller path */
private String path;
/* Flag that indicates having a resource path in the list of the RouteParts */
private boolean hasControllerPath = false;
/* Must match with ContentType header in the client's model */
private List<MediaType> consumes = singletonList(WILDCARD);
/* Final MediaType of the Response is determined by the Accept header in the client's model */
private List<MediaType> produces = singletonList(WILDCARD);
/* Collection of interceptors which will be applied with execution of this route */
private List<Interceptor> interceptors = new ArrayList<>();
/* Collection of roles which protect this route */
private List<String> roles = new ArrayList<>();
@SuppressWarnings("rawtypes")
private RouteAction action;
private Type<?> requestType;
private Type<?> responseType;
public InternalRoute(String path, HttpMethod httpMethod,
RouteAction action, Type<?> requestClazz, Type<?> responseClazz) {
this.path = path;
this.httpMethod = httpMethod;
this.action = action;
this.requestType = requestClazz;
this.responseType = responseClazz;
this.routeParts = createRouteParts(path);
}
@Override
public Route consumes(MediaType... consumes) {
this.consumes = asList(consumes);
return this;
}
public List<MediaType> getConsumes() {
return unmodifiableList(consumes);
}
@Override
public Route produces(MediaType... produces) {
this.produces = asList(produces);
return this;
}
public List<MediaType> getProduces() {
return unmodifiableList(produces);
}
private List<RoutePart<?>> createRouteParts(String path) {
return createPathParts(path).stream()
.map(new ParamParser(path))
.peek(part -> {
// Save path params to the map
if (part.getType() == RoutePart.Type.PARAM) {
pathParams.put(part.getValue(), part);
}
})
.collect(toList());
}
public List<RoutePart<?>> getRouteParts() {
return isNull(routeParts) ? new ArrayList<>() : unmodifiableList(routeParts);
}
public String getPath() {
return path;
}
public Map<String, RoutePart<?>> getPathParams() {
return unmodifiableMap(pathParams);
}
public HttpMethod getHttpMethod() {
return httpMethod;
}
public void addControllerPath(List<RoutePart<String>> parts) {
if (!hasControllerPath) {
routeParts.addAll(0, parts);
path = addControllerPathToPath(parts);
hasControllerPath = true;
} else {
logger.warn(() -> "A controller path has been already set.");
}
}
private String addControllerPathToPath(List<RoutePart<String>> parts) {
String basePath = parts.stream()
.map(RoutePart::getValue)
.collect(joining(SLASH, SLASH, ""));
return SLASH.contains(path) ? basePath : basePath + path;
}
@Override
public Route interceptor(Interceptor... interceptor) {
interceptors.addAll(asList(interceptor));
return this;
}
public Type getRequestType() {
return requestType;
}
public Type<?> getResponseType() {
return responseType;
}
public boolean hasRequestBody() {
return Objects.nonNull(requestType);
}
@SuppressWarnings("unchecked")
public InternalResponse<Object> execute(InternalRequest<Object> request, InternalResponse<Object> response) {
action.perform(ImmutableRequest.of(request), response);
return response;
}
public List<Interceptor> getInterceptors() {
return unmodifiableList(interceptors);
}
public Optional<Reader> getReader(MediaType mediaType) {
return Optional.ofNullable(readers.get(mediaType));
}
public Map<MediaType, Reader> getReaders() {
return readers;
}
public void setReaders(Map<MediaType, Reader> readers) {
requireNonNull(readers);
this.readers = readers;
}
public Map<MediaType, Writer> getWriters() {
return writers;
}
public void setWriters(Map<MediaType, Writer> writers) {
requireNonNull(writers);
this.writers = writers;
}
public void addReader(Reader reader) {
requireNonNull(reader);
this.readers.put(reader.getMediaType(), reader);
}
public Optional<Writer> getWriter(MediaType mediaType) {
return Optional.ofNullable(writers.get(mediaType));
}
public void addWriter(Writer writer) {
requireNonNull(writer);
this.writers.put(writer.getMediaType(), writer);
}
@Override
public Route roles(String... roles) {
this.roles = asList(roles);
return this;
}
public List<String> getRoles() {
return unmodifiableList(roles);
}
public boolean isSecured() {
return nonEmpty(roles);
}
@Override
public int hashCode() {
return Objects.hash(httpMethod, path);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
@SuppressWarnings("unchecked")
final InternalRoute other = (InternalRoute) obj;
return Objects.equals(this.httpMethod, other.httpMethod)
&& Objects.equals(this.path, other.path);
}
}