/*
* Copyright WSO2, Inc. (http://wso2.com)
*
* 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.wso2.carbon.cloud.gateway.agent;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNode;
import org.apache.axis2.description.AxisService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.commons.schema.XmlSchema;
import org.wso2.carbon.cloud.gateway.common.CGException;
//import org.wso2.carbon.cloud.gateway.common.CGServiceDependencyBean;
import org.wso2.carbon.cloud.gateway.stub.types.common.CGServiceDependencyBean;
/**
* Scans a given wsdl (and axis service associated with it) for schama
* import/includes and adjust them accordingly so that the given axis service
* can be constructed using the wsdl outside the firewall. This is required
* because, CSG server (running outside a firewall) doesn't have access to these
* external dependencies.
*/
public class CGAgentWsdlDependencyResolver {
/**
* Local name of the sschema tag
*/
private static final String SCHEMA_TAG = "schema";
/**
* Local name of wsdl:types tag
*/
private static final String TYPES_TAG = "types";
/**
* QName to select 'schemaLocation' from a xsd:include or xsd:import tag.
*/
private static final QName SCHEMA_LOCATION_QNAME = new QName(AxisService.SCHEMA_LOCATION);
private static Log log = LogFactory.getLog(CGAgentWsdlDependencyResolver.class);
/**
* The Service that will be published onto a CSG server
*/
private AxisService service;
/**
* Location of the Wsdl associated with the service (that needs to be
* published onto a service)
*/
private String wsdlLocation;
/**
* Instantiates an instance using specified arguments
*
* @param service The service that needs to be checked for schema imports
* @param wsdlLocation location of the wsdl
*/
public CGAgentWsdlDependencyResolver(AxisService service, String wsdlLocation) {
this.service = service;
this.wsdlLocation = wsdlLocation;
}
/**
* Scan the supplied wsdl (and Axis service) for schema imports and does
* following:
* <ul>
* <li>Adjust the 'schemalocation' tags on each import/include element of
* the wsdl so they will not point to locations that are not accessible.
* <li>Prepare a list of {@link CGServiceDependencyBean} containing content
* of imported schemas so they can be transmitted outside the firewall.
* </ul>
*
* @param dependencies This list will be populated with
* {@link CGServiceDependencyBean}s
* @return The Wsdl with modified 'schemaLocations'
* @throws CGException An error while retrieving the wsdl
*/
@SuppressWarnings("unchecked")
public OMElement parseWsdlDependencies(List<CGServiceDependencyBean> dependencies)
throws CGException {
// Force axis service to populate schema mappings
service.populateSchemaMappings(false);
// Retrieve the wsdl document as a OMElement
OMElement wsdlElement = CGAgentUtils.getWSDLElement(wsdlLocation);
try {
String wsdlString = wsdlElement.toStringWithConsume();
if (wsdlString.contains("Unable to generate WSDL 1.1 for this service") ||
wsdlString.contains("error")) {
// axis2 doesn't generate a WSDL for WSDL 1.1 for REST service if useoriginalwsdl
// parameter is not given
return null;
}
} catch (XMLStreamException e) {
log.error("Error while parsing the WSDL", e);
return null;
}
Map<Object, Object> schemaMappingTable = service.getSchemaMappingTable();
Map<String, OMElement> importedOrIncludedSchemaElements = new HashMap<String, OMElement>();
// Search for wsdl:types tag
OMElement typesElement =
wsdlElement.getFirstChildWithName(
new QName(wsdlElement.getNamespace().getNamespaceURI(),
TYPES_TAG));
if (typesElement != null) {
Iterator<OMNode> schemaIterator = typesElement.getChildrenWithLocalName(SCHEMA_TAG);
// traverse all the schema tags and find all the schema imports and
// includes
while (schemaIterator.hasNext()) {
OMNode node = schemaIterator.next();
if (node instanceof OMElement) {
OMElement schemaElement = (OMElement) node;
Iterator<OMNode> iterator = schemaElement.getChildren();
while (iterator.hasNext()) {
OMNode child = iterator.next();
if (isImportOrIncludeTag(child)) {
// Found a schema include or a import
String schemaLocation =
((OMElement) child).getAttributeValue(SCHEMA_LOCATION_QNAME);
if (log.isDebugEnabled()) {
log.debug("Found schema import or include : " + child.toString() +
" in wsdl at :" + wsdlLocation);
}
importedOrIncludedSchemaElements.put(schemaLocation, (OMElement) child);
}
}
}
}
}
SimpleSchemaNameGenerator nameGenerator =
new SimpleSchemaNameGenerator(SCHEMA_TAG,
service.getName());
if (log.isDebugEnabled()) {
log.debug("Traversing schemas in axis service: " + service.getName());
}
// 1. Retrieve content of all schemas from axis service and prepare
// CSGServiceDependencyBeans
// 2. Adjust the schemaLocation values so that they will correctly match
// the keys in
// CSGServiceDependencyBeans
for (Entry<Object, Object> entry : schemaMappingTable.entrySet()) {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
XmlSchema schema = (XmlSchema) entry.getValue();
schema.write(bao);
CGServiceDependencyBean dependency = new CGServiceDependencyBean();
String keyInAxisServiceSchema = entry.getKey().toString();
OMElement externalSchemaRefInWsdl =
getMatchingSchemaImportInWsdl(importedOrIncludedSchemaElements,
keyInAxisServiceSchema);
if (externalSchemaRefInWsdl != null) {
String generatedSchemaName = nameGenerator.getNext();
externalSchemaRefInWsdl.getAttribute(SCHEMA_LOCATION_QNAME)
.setAttributeValue(generatedSchemaName);
dependency.setKey(generatedSchemaName);
} else {
dependency.setKey(keyInAxisServiceSchema);
}
dependency.setContent(bao.toString());
dependencies.add(dependency);
}
return wsdlElement;
}
/**
* A simple utility class to generate schema names based on a counter.
*/
private class SimpleSchemaNameGenerator {
private int count;
private String prefix;
private String serviceName;
public SimpleSchemaNameGenerator(String prefix, String serviceName) {
this.prefix = prefix;
this.serviceName = serviceName;
count = 0;
}
public String getNext() {
return String.format("%s%s_%d", this.prefix, this.serviceName, count++);
}
}
/**
* Matches schemalocation of a xsd import/include with a given key. This is
* required since keys of schema mapping table in {@link AxisService}
* doesn't correctly match the values of 'schemaLocations' in wsdl
*
* @param schemaImports Contains schema import/include of elements extracted from wsdl
* @param keyInAxisServiceSchema The key in schema mapping table in {@link AxisService} A key
* returned by associated axis service.
* @return the matching schema import/include tag or null if none matched
* with provided key
*/
private OMElement getMatchingSchemaImportInWsdl(Map<String, OMElement> schemaImports,
String keyInAxisServiceSchema) {
OMElement matchingSchemaElement = null;
for (String schemaLocationKey : schemaImports.keySet()) {
if (schemaLocationKey.contains(keyInAxisServiceSchema)) {
matchingSchemaElement = schemaImports.get(schemaLocationKey);
break;
}
}
return matchingSchemaElement;
}
/**
* Determines weather the specified node is a xsd:import or xsd:include tag
*
* @param node The xml node
* @return True if the node is a import or a include (false otherwise)
*/
private boolean isImportOrIncludeTag(OMNode node) {
boolean isImportOrInclude = false;
if (node instanceof OMElement) {
OMElement element = (OMElement) node;
isImportOrInclude =
element.getLocalName().equals(AxisService.IMPORT_TAG) ||
element.getLocalName().equals(AxisService.INCLUDE_TAG);
}
return isImportOrInclude;
}
}