/**
* Copyright 2015 Nortal 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 com.nortal.jroad.mapping;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.server.endpoint.mapping.AbstractEndpointMapping;
import org.w3c.dom.Element;
import com.nortal.jroad.annotation.XTeeService;
import com.nortal.jroad.endpoint.AbstractXTeeBaseEndpoint;
import com.nortal.jroad.endpoint.ListMethodsEndpoint;
import com.nortal.jroad.enums.XRoadProtocolVersion;
import com.nortal.jroad.util.SOAPUtil;
import com.nortal.jroad.wsdl.XTeeWsdlDefinition;
/**
* Finds all X-Road endpoints and maps incoming requests to them according to query name present in the X-Road header.
*
* @author Dmitri Danilkin
* @author Lauri Lättemäe (lauri.lattemae@nortal.com) - protocol 4.0
*/
@Component
public class XTeeEndpointMapping extends AbstractEndpointMapping implements InitializingBean {
protected static final Logger log = Logger.getLogger(XTeeEndpointMapping.class);
@Resource(name = "xteeDatabase")
private String xRoadDatabase;
private Map<String, AbstractXTeeBaseEndpoint> methodMap;
public void setXteeDatabase(String xRoadDatabase) {
this.xRoadDatabase = xRoadDatabase;
}
// Lazy initialization
public void afterPropertiesSet() throws Exception {
log.debug("Initializing method map...");
methodMap = new HashMap<String, AbstractXTeeBaseEndpoint>();
String[] beans = getApplicationContext().getBeanNamesForType(AbstractXTeeBaseEndpoint.class);
for (int i = 0; i < beans.length; i++) {
AbstractXTeeBaseEndpoint endpoint = (AbstractXTeeBaseEndpoint) getApplicationContext().getBean(beans[i]);
String meetod = getXRoadMethodName(endpoint.getClass(), xRoadDatabase);
if (methodMap.get(meetod) != null) {
throw new IllegalStateException("Unresolvable: endpoints " + endpoint.getClass().getSimpleName() + " and "
+ methodMap.get(meetod).getClass().getSimpleName() + " have the same XRoad method identifier '" + meetod
+ "'!");
}
if (!(endpoint instanceof ListMethodsEndpoint)) {
methodMap.put(meetod, endpoint);
}
if (log.isDebugEnabled()) {
log.debug("Mapping XRoad method '" + meetod + "' to " + endpoint.getClass().getSimpleName());
}
}
log.debug("Method mappings initialized.");
}
@Override
protected Object getEndpointInternal(MessageContext messageCtx) throws Exception {
SOAPMessage message = SOAPUtil.extractSoapMessage(messageCtx.getRequest());
if (message.getSOAPHeader() != null) {
AbstractXTeeBaseEndpoint endpoint = methodMap.get(getRequestMethod(message.getSOAPHeader()));
if (endpoint != null) {
if (log.isDebugEnabled()) {
log.debug("Matched " + endpoint + " to " + endpoint.getClass().getSimpleName());
}
return endpoint;
}
}
try {
if (SOAPUtil.getNodeByXPath(message.getSOAPBody(), "/*/*/*[local-name()='listMethods']") != null) {
log.debug("Matched headerless listMethods request.");
return getApplicationContext().getBean(getApplicationContext().getBeanNamesForType(ListMethodsEndpoint.class)[0]);
}
} catch (NullPointerException e) {
// ListMethods lookup failed
}
return null;
}
protected String getRequestMethod(SOAPHeader header) {
if (header == null) {
return null;
}
// Try to extract v2 protocol headers
Element nimi = SOAPUtil.getNsElement(header, "nimi", XRoadProtocolVersion.V2_0.getNamespaceUri());
if (nimi != null) {
return SOAPUtil.getTextContent(nimi);
}
// Try to extract v4 protocol headers
Element service = SOAPUtil.getNsElement(header, "service", XTeeWsdlDefinition.XROAD_NAMESPACE);
if (service == null) {
return null;
}
String serviceCode = SOAPUtil.getNsElementValue(service, "serviceCode", XTeeWsdlDefinition.XROAD_IDEN_NAMESPACE);
String serviceVersion =
SOAPUtil.getNsElementValue(service, "serviceVersion", XTeeWsdlDefinition.XROAD_IDEN_NAMESPACE);
StringBuilder method = new StringBuilder();
method.append(xRoadDatabase).append(".");
method.append(serviceCode).append(".");
method.append(serviceVersion != null ? serviceVersion : "v1");
return method.toString();
}
/**
* Receives the string and returns it with first character set to lowercase and suffix "Endpoint" removed.
*
* @param className unqualified class name
* @return input string <code>className</code> with first character in lowercase and "Endpoint" suffix removed
*/
private String getServiceName(String className) {
String result;
if (className.endsWith("Endpoint")) {
result = className.substring(0, className.length() - 8);
} else {
result = className;
}
return result;
}
/**
* Attempts to get the full XRoad method name for the given {@link AbstractXTeeBaseEndpoint}, by processing the
* {@link XRoadService} annotation -- if this is not present the method name will be a concatenation of X-Tee database
* name, unqualified class name of given {@link AbstractXTeeBaseEndpoint} (as service name) and "v1" (as version
* number).
*
* @param clazz XRoad service endpoint implementation class
* @param databaseName name of the XRoad database
* @return the XRoadService method name that was constructed according to aforementioned rules
*/
private String getXRoadMethodName(Class<? extends AbstractXTeeBaseEndpoint> clazz, String databaseName) {
String version = "v1";
String serviceName = null;
if (clazz.isAnnotationPresent(XTeeService.class)) {
XTeeService serviceAnnotation = clazz.getAnnotation(XTeeService.class);
version = serviceAnnotation.version();
if (!serviceAnnotation.name().equals("") || !serviceAnnotation.value().equals("")) {
serviceName = serviceAnnotation.name().equals("") ? serviceAnnotation.value() : serviceAnnotation.name();
}
}
if (serviceName == null) {
serviceName = getServiceName(clazz.getSimpleName());
}
StringBuilder sb = new StringBuilder(databaseName).append(".").append(serviceName).append(".").append(version);
return sb.toString();
}
public Collection<String> getMethods() {
return methodMap.keySet();
}
public Map<String, AbstractXTeeBaseEndpoint> getMethodMap() {
return Collections.unmodifiableMap(methodMap);
}
}