/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.synapse.endpoints;
import com.damnhandy.uri.template.UriTemplate;
import com.damnhandy.uri.template.impl.ExpressionParseException;
import org.apache.axis2.Constants;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseConstants;
import org.apache.synapse.mediators.MediatorProperty;
import org.apache.synapse.rest.RESTConstants;
import org.apache.synapse.util.xpath.SynapseXPath;
import java.io.UnsupportedEncodingException;
import java.net.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class HTTPEndpoint extends AbstractEndpoint {
private UriTemplate uriTemplate;
private String httpMethod;
private SynapseXPath httpMethodExpression;
private boolean legacySupport = false; // this is to support backward compatibility
public static String legacyPrefix = "legacy-encoding:";
/*Todo*/
/*Do we need HTTP Headers here?*/
public void onFault(MessageContext synCtx) {
// For setting Car name (still for Proxy)
logSetter();
// is this really a fault or a timeout/connection close etc?
if (isTimeout(synCtx)) {
getContext().onTimeout();
} else if (isSuspendFault(synCtx)) {
getContext().onFault();
}
// this should be an ignored error if we get here
setErrorOnMessage(synCtx, null, null);
super.onFault(synCtx);
}
public void onSuccess() {
if (getContext() != null) {
getContext().onSuccess();
}
}
public void send(MessageContext synCtx) {
// For setting Car name (still for Proxy)
logSetter();
executeEpTypeSpecificFunctions(synCtx);
if (getParentEndpoint() == null && !readyToSend()) {
// if the this leaf endpoint is too a root endpoint and is in inactive
informFailure(synCtx, SynapseConstants.ENDPOINT_ADDRESS_NONE_READY,
"Currently , Address endpoint : " + getContext());
} else {
super.send(synCtx);
}
}
public void executeEpTypeSpecificFunctions(MessageContext synCtx) {
processUrlTemplate(synCtx);
processHttpMethod(synCtx);
}
private void processHttpMethod(MessageContext synCtx) {
if (httpMethod != null) {
super.getDefinition().setHTTPEndpoint(true);
synCtx.setProperty(Constants.Configuration.HTTP_METHOD, httpMethod);
}
// Method is not a mandatory parameter for HttpEndpoint. So httpMethod can be null.
// http method from incoming message is used as the http method
}
private String decodeString(String value) {
if (value == null) {
return "";
}
try {
return URLDecoder.decode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
if(log.isDebugEnabled()) {
log.warn("Encoding is not supported", e);
}
return value;
}
}
private void processUrlTemplate(MessageContext synCtx) throws ExpressionParseException {
Map<String, Object> variables = new HashMap<String, Object>();
/*The properties with uri.var.* are only considered for Outbound REST Endpoints*/
Set propertySet = synCtx.getPropertyKeySet();
// We have to create a local UriTemplate object, or else the UriTemplate.set(variables) call will fill up a list of variables and since uriTemplate
// is not thread safe, this list won't be clearing.
UriTemplate template = null;
String evaluatedUri = "";
// legacySupport for backward compatibility where URI Template decoding handled via HTTPEndpoint
if (legacySupport) {
for (Object propertyKey : propertySet) {
if (propertyKey.toString() != null&&
(propertyKey.toString().startsWith(RESTConstants.REST_URI_VARIABLE_PREFIX)
|| propertyKey.toString().startsWith(RESTConstants.REST_QUERY_PARAM_PREFIX))) {
Object objProperty = synCtx.getProperty(propertyKey.toString());
if (objProperty != null) {
if (objProperty instanceof String) {
variables.put(propertyKey.toString(),
decodeString((String) synCtx.getProperty(propertyKey.toString())));
} else {
variables.put(propertyKey.toString(),
decodeString(String.valueOf(synCtx.getProperty(propertyKey.toString()))));
}
}
}
}
// Include properties defined at endpoint.
Iterator endpointProperties = getProperties().iterator();
while(endpointProperties.hasNext()) {
MediatorProperty property = (MediatorProperty) endpointProperties.next();
if(property.getName().toString() != null
&& (property.getName().toString().startsWith(RESTConstants.REST_URI_VARIABLE_PREFIX) ||
property.getName().toString().startsWith(RESTConstants.REST_QUERY_PARAM_PREFIX) )) {
variables.put(property.getName(), decodeString((String) property.getValue()));
}
}
template = UriTemplate.fromTemplate(uriTemplate.getTemplate());
if(template != null) {
template.set(variables);
}
if(variables.isEmpty()){
evaluatedUri = template.getTemplate();
} else {
try {
// Decode needs to avoid replacing special characters(e.g %20 -> %2520) when creating URL.
String decodedString = URLDecoder.decode(template.expand(), "UTF-8");
URL url = new URL(decodedString);
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(),
url.getPath(), url.getQuery(), url.getRef());// this to avoid url.toURI which causes exceptions
evaluatedUri = uri.toURL().toString();
if (log.isDebugEnabled()) {
log.debug("Expanded URL : " + evaluatedUri);
}
} catch (URISyntaxException e) {
if (log.isDebugEnabled()) {
log.debug("Invalid URL syntax for HTTP Endpoint: " + this.getName(), e);
}
evaluatedUri = template.getTemplate();
} catch(ExpressionParseException e) {
log.debug("No URI Template variables defined in HTTP Endpoint: " + this.getName());
evaluatedUri = template.getTemplate();
} catch(MalformedURLException e) {
log.debug("Invalid URL for HTTP Endpoint: " + this.getName());
evaluatedUri = template.getTemplate();
} catch(UnsupportedEncodingException e) {
log.debug("Exception while decoding the URL in HTTP Endpoint: " + this.getName());
evaluatedUri = template.getTemplate();
}
}
} else { // URI Template encoding not handled by HTTP Endpoint, compliant with RFC6570
for (Object propertyKey : propertySet) {
if (propertyKey.toString() != null&&
(propertyKey.toString().startsWith(RESTConstants.REST_URI_VARIABLE_PREFIX)
|| propertyKey.toString().startsWith(RESTConstants.REST_QUERY_PARAM_PREFIX))) {
Object objProperty =
synCtx.getProperty(propertyKey.toString());
if (objProperty != null) {
if (objProperty instanceof String) {
variables.put(propertyKey.toString(),
(String) synCtx.getProperty(propertyKey.toString()));
} else {
variables.put(propertyKey.toString(),
(String) String.valueOf(synCtx.getProperty(propertyKey.toString())));
}
}
}
}
// Include properties defined at endpoint.
Iterator endpointProperties = getProperties().iterator();
while(endpointProperties.hasNext()) {
MediatorProperty property = (MediatorProperty) endpointProperties.next();
if(property.getName().toString() != null
&& (property.getName().toString().startsWith(RESTConstants.REST_URI_VARIABLE_PREFIX) ||
property.getName().toString().startsWith(RESTConstants.REST_QUERY_PARAM_PREFIX) )) {
variables.put(property.getName(), (String) property.getValue());
}
}
String tmpl;
// This is a special case as we want handle {uri.var.variable} having full URL (except as a path param or query param)
// this was used in connectors Eg:- uri-template="{uri.var.variable}"
if(uriTemplate.getTemplate().charAt(0)=='{' && uriTemplate.getTemplate().charAt(1)!='+'){
tmpl = "{+" + uriTemplate.getTemplate().substring(1);
} else {
tmpl = uriTemplate.getTemplate();
}
template = UriTemplate.fromTemplate(tmpl);
if(template != null) {
template.set(variables);
}
if(variables.isEmpty()){
evaluatedUri = template.getTemplate();
} else {
try {
URI uri = new URI(template.expand());
evaluatedUri = uri.toString();
if (log.isDebugEnabled()) {
log.debug("Expanded URL : " + evaluatedUri);
}
} catch (URISyntaxException e) {
if (log.isDebugEnabled()) {
log.debug("Invalid URL syntax for HTTP Endpoint: " + this.getName(), e);
}
evaluatedUri = template.getTemplate();
} catch(ExpressionParseException e) {
log.debug("No URI Template variables defined in HTTP Endpoint: " + this.getName());
evaluatedUri = template.getTemplate();
}
}
}
if (evaluatedUri != null) {
synCtx.setTo(new EndpointReference(evaluatedUri));
if (super.getDefinition() != null) {
synCtx.setProperty(EndpointDefinition.DYNAMIC_URL_VALUE, evaluatedUri);
}
}
}
public String getHttpMethod() {
return httpMethod;
}
public void setHttpMethod(String httpMethod) {
this.httpMethod = httpMethod;
}
public UriTemplate getUriTemplate() {
return uriTemplate;
}
public SynapseXPath getHttpMethodExpression() {
return httpMethodExpression;
}
public void setUriTemplate(UriTemplate uriTemplate) {
this.uriTemplate = uriTemplate;
}
public void setHttpMethodExpression(SynapseXPath httpMethodExpression) {
this.httpMethodExpression = httpMethodExpression;
}
public boolean isLegacySupport() {
return legacySupport;
}
public void setLegacySupport(boolean legacySupport) {
this.legacySupport = legacySupport;
}
}