Java tutorial
/* * Copyright (C) 2016 Cognifide Limited * * 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 io.knotx.knot.action; import static io.knotx.knot.action.domain.FormConstants.FORM_NO_REDIRECT_SIGNAL; import static io.knotx.knot.action.domain.FormConstants.FRAGMENT_KNOT_PREFIX; import io.knotx.dataobjects.AdapterRequest; import io.knotx.dataobjects.AdapterResponse; import io.knotx.dataobjects.ClientRequest; import io.knotx.dataobjects.ClientResponse; import io.knotx.dataobjects.KnotContext; import io.knotx.http.AllowedHeadersFilter; import io.knotx.http.MultiMapCollector; import io.knotx.knot.AbstractKnotProxy; import io.knotx.knot.action.domain.FormEntity; import io.knotx.knot.action.domain.FormSimplifier; import io.knotx.knot.action.domain.FormsFactory; import io.knotx.rxjava.proxy.AdapterProxy; import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpMethod; import io.vertx.core.json.JsonObject; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import io.vertx.rxjava.core.MultiMap; import io.vertx.rxjava.core.Vertx; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import rx.Single; public class ActionKnotProxyImpl extends AbstractKnotProxy { private static final Logger LOGGER = LoggerFactory.getLogger(ActionKnotVerticle.class); private final Vertx vertx; private final ActionKnotConfiguration configuration; private final FormSimplifier simplifier; ActionKnotProxyImpl(Vertx vertx, ActionKnotConfiguration configuration, FormSimplifier simplifier) { this.vertx = vertx; this.configuration = configuration; this.simplifier = simplifier; } @Override public Single<KnotContext> processRequest(final KnotContext knotContext) { return Single.just(knotContext).map(context -> FormsFactory.create(context, configuration)) .flatMap(forms -> { if (knotContext.getClientRequest().getMethod() == HttpMethod.GET) { return Single.just(handleGetMethod(forms, knotContext)); } else { FormEntity current = currentForm(forms, knotContext); return callActionAdapter(knotContext, current) .map(response -> processAdapterResponse(knotContext, forms, current, response)); } }).onErrorReturn(error -> processError(knotContext, error)); } @Override protected boolean shouldProcess(Set<String> knots) { return knots.stream().anyMatch(knot -> knot.startsWith(FRAGMENT_KNOT_PREFIX)); } @Override protected KnotContext processError(KnotContext context, Throwable error) { LOGGER.error("Could not process template [{}]", error, context.getClientRequest().getPath()); KnotContext errorResponse = new KnotContext().setClientResponse(context.getClientResponse()); errorResponse.getClientResponse().setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code()); return errorResponse; } private KnotContext handleGetMethod(List<FormEntity> forms, KnotContext knotContext) { LOGGER.trace("Pass-through {} request", knotContext.getClientRequest().getMethod()); knotContext.setTransition(DEFAULT_TRANSITION); forms.forEach(form -> form.fragment().content(simplifier.simplify(form.fragment().content(), configuration.formIdentifierName(), form.identifier()))); return knotContext; } private Single<AdapterResponse> callActionAdapter(KnotContext knotContext, FormEntity current) { LOGGER.trace("Process form for {} ", knotContext); AdapterProxy adapter = AdapterProxy.createProxy(vertx, current.adapter().getAddress()); return adapter.rxProcess(prepareAdapterRequest(knotContext, current.adapter())); } private AdapterRequest prepareAdapterRequest(KnotContext knotContext, ActionKnotConfiguration.AdapterMetadata metadata) { ClientRequest request = new ClientRequest().setPath(knotContext.getClientRequest().getPath()) .setMethod(knotContext.getClientRequest().getMethod()) .setFormAttributes(knotContext.getClientRequest().getFormAttributes()) .setHeaders(getFilteredHeaders(knotContext.getClientRequest().getHeaders(), metadata.getAllowedRequestHeaders())); AdapterRequest adapterRequest = new AdapterRequest().setRequest(request) .setParams(new JsonObject(metadata.getParams())); LOGGER.info("Adapter [{}] call with request [{}]", metadata.getAddress(), adapterRequest); return adapterRequest; } private KnotContext processAdapterResponse(KnotContext knotContext, List<FormEntity> forms, FormEntity form, AdapterResponse response) { final ClientResponse clientResponse = response.getResponse(); final String signal = response.getSignal(); if (HttpResponseStatus.OK.code() != clientResponse.getStatusCode()) { return errorKnotResponse(clientResponse, knotContext, form); } else { String redirectLocation = form.url(signal).orElse(FORM_NO_REDIRECT_SIGNAL); return shouldRedirect(redirectLocation) ? redirectKnotResponse(knotContext, form, clientResponse, redirectLocation) : routeToNextKnotResponse(clientResponse, knotContext, forms, form); } } private KnotContext routeToNextKnotResponse(ClientResponse clientResponse, KnotContext knotContext, List<FormEntity> forms, FormEntity form) { LOGGER.trace("Request next transition to [{}]", DEFAULT_TRANSITION); JsonObject actionContext = new JsonObject() .put("_result", new JsonObject(clientResponse.getBody().toString())) .put("_response", clientResponse.toMetadataJson()); form.fragment().context().put("action", actionContext); knotContext.getClientResponse().setHeaders( getFilteredHeaders(clientResponse.getHeaders(), form.adapter().getAllowedResponseHeaders())); forms.forEach(f -> f.fragment().content( simplifier.simplify(f.fragment().content(), configuration.formIdentifierName(), f.identifier()))); knotContext.setTransition(DEFAULT_TRANSITION); return knotContext; } private KnotContext redirectKnotResponse(KnotContext knotContext, FormEntity form, ClientResponse clientResponse, String redirectLocation) { LOGGER.trace("Request redirected to [{}]", redirectLocation); knotContext.getClientResponse().setStatusCode(HttpResponseStatus.MOVED_PERMANENTLY.code()); MultiMap headers = MultiMap.caseInsensitiveMultiMap(); headers.addAll(getFilteredHeaders(clientResponse.getHeaders(), form.adapter().getAllowedResponseHeaders())); headers.add(HttpHeaders.LOCATION.toString(), redirectLocation); knotContext.getClientResponse().setHeaders(headers); knotContext.clearFragments(); return knotContext; } private KnotContext errorKnotResponse(ClientResponse clientResponse, KnotContext knotContext, FormEntity form) { knotContext.getClientResponse().setStatusCode(clientResponse.getStatusCode()) .setHeaders( getFilteredHeaders(clientResponse.getHeaders(), form.adapter().getAllowedResponseHeaders())) .setBody(null); knotContext.clearFragments(); return knotContext; } private MultiMap getFilteredHeaders(MultiMap headers, List<Pattern> allowedHeaders) { return headers.names().stream().filter(AllowedHeadersFilter.create(allowedHeaders)) .collect(MultiMapCollector.toMultiMap(o -> o, headers::getAll)); } private FormEntity currentForm(List<FormEntity> forms, KnotContext knotContext) { return forms.stream().filter(form -> form.current(knotContext, configuration.formIdentifierName())) .findFirst().orElseThrow(() -> { LOGGER.error("No form attribute [{}] matched with forms identifiers [{}]", knotContext.getClientRequest().getFormAttributes(), forms.stream().map(FormEntity::identifier).toArray()); return new IllegalStateException("Could not match form identifiers!"); }); } private boolean shouldRedirect(String signal) { return StringUtils.isNotEmpty(signal) && !FORM_NO_REDIRECT_SIGNAL.equals(signal); } }