/*
* Copyright (c) 2008, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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.registry.extensions.handlers.utils;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.jdbc.handlers.RequestContext;
import org.wso2.carbon.registry.core.*;
import org.wso2.carbon.registry.core.utils.RegistryUtils;
import org.wso2.carbon.registry.extensions.utils.CommonConstants;
import org.wso2.carbon.registry.extensions.utils.CommonUtil;
import org.wso2.carbon.registry.extensions.utils.WSDLUtil;
import org.wso2.carbon.registry.extensions.utils.WSDLValidationInfo;
import org.apache.ws.commons.schema.XmlSchema;
import org.apache.ws.commons.schema.XmlSchemaCollection;
import org.apache.ws.commons.schema.XmlSchemaExternal;
import org.apache.ws.commons.schema.XmlSchemaObjectCollection;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.InputSource;
import javax.wsdl.Types;
import javax.wsdl.extensions.schema.Schema;
import java.util.*;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.net.URISyntaxException;
public class SchemaProcessor {
Registry registry;
Registry systemRegistry;
ArrayList<String> processedSchemas;
ArrayList<String> visitedSchemas;
HashMap<String, SchemaInfo> schemas;
ArrayList<Association> associations;
private String baseURI = null;
private WSDLValidationInfo validationInfo;
private static final String SCHEMA_VALIDATION_MESSAGE = "Schema Validation Message ";
private static final String SCHEMA_STATUS = "Schema Validation";
private String resourceName = "";
private static final Log log = LogFactory.getLog(SchemaProcessor.class);
private int i;
public SchemaProcessor(RequestContext requestContext, WSDLValidationInfo validationInfo) {
this.registry = requestContext.getRegistry();
try {
this.systemRegistry = CommonUtil.getUnchrootedSystemRegistry(requestContext);
} catch (RegistryException ignore) {
this.systemRegistry = null;
}
i = 0;
schemas = new HashMap<String, SchemaInfo> ();
processedSchemas = new ArrayList<String>();
visitedSchemas = new ArrayList<String>();
associations = new ArrayList<Association>();
this.validationInfo = validationInfo;
}
/* Save the schema, schema imports, associations in the registry. Intended to used by the XSD Media-type handler
only.
*/
public String putSchemaToRegistry( RequestContext requestContext,
String resourcePath,
String commonLocation,
boolean processIncludes) throws RegistryException {
resourceName = resourcePath.substring(resourcePath.lastIndexOf(RegistryConstants.PATH_SEPARATOR) + 1);
XmlSchemaCollection xmlSchemaCollection = new XmlSchemaCollection();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream((byte[])requestContext.getResource().getContent());
InputSource inputSource = new InputSource(byteArrayInputStream);
String url = "http://this.schema.needs/a/valid/source/url/to/proceed.xsd";
try {
XmlSchema xmlSchema = xmlSchemaCollection.read(inputSource, null);
xmlSchema.setSourceURI(url);
evaluateSchemasRecursively(xmlSchema, null, false, true);
} catch (RuntimeException re) {
String msg = "Could not read the XML Schema Definition file. ";
if (re.getCause() instanceof org.apache.ws.commons.schema.XmlSchemaException) {
msg += re.getCause().getMessage();
log.error(msg, re);
throw new RegistryException(msg);
}
throw new RegistryException(msg, re);
}
updateSchemaPaths(commonLocation);
updateSchemaInternalsAndAssociations();
String symlinkLocation = RegistryUtils.getAbsolutePath(requestContext.getRegistryContext(),
requestContext.getResource().getProperty(RegistryConstants.SYMLINK_PROPERTY_NAME));
Resource metaResource = requestContext.getResource();
String path = saveSchemaToRegistry(resourcePath, symlinkLocation, metaResource); // should depend on the central location / relative location flag
persistAssociations();
return path;
}
/* Save the schema, schema imports, associations in the registry. Intended to used by the XSD Media-type handler
only.
*/
public String importSchemaToRegistry(RequestContext requestContext,
String resourcePath,
String commonLocation,
boolean processIncludes) throws RegistryException {
resourceName = resourcePath.substring(resourcePath.lastIndexOf(RegistryConstants.PATH_SEPARATOR) + 1);
String url = requestContext.getSourceURL();
XmlSchemaCollection xmlSchemaCollection = new XmlSchemaCollection();
xmlSchemaCollection.setBaseUri(url);
baseURI = url;
InputSource inputSource = new InputSource(url);
try {
// Here we assue schema is correct. Schema validation is beyond our scope, so we don't
// bother with a ValidationEventHandler.
XmlSchema xmlSchema = xmlSchemaCollection.read(inputSource, null);
evaluateSchemasRecursively(xmlSchema, null, false, true);
} catch (RuntimeException re) {
String msg = "Could not read the XML Schema Definition file. ";
if (re.getCause() instanceof org.apache.ws.commons.schema.XmlSchemaException) {
msg += re.getCause().getMessage();
log.error(msg, re);
throw new RegistryException(msg);
}
throw new RegistryException(msg, re);
}
updateSchemaPaths(commonLocation);
updateSchemaInternalsAndAssociations();
String symlinkLocation = requestContext.getResource().getProperty(RegistryConstants.SYMLINK_PROPERTY_NAME);
Resource metaResource = requestContext.getResource();
String path = saveSchemaToRegistry(resourcePath, symlinkLocation, metaResource); // should depend on the central location / relative location flag
persistAssociations();
return path;
}
public void evaluateSchemas(
Types types,
String wsdlDocumentBaseURI,
boolean evaluateImports,
ArrayList<String> dependencies) throws RegistryException {
baseURI = wsdlDocumentBaseURI;
/* evaluating schemas found under wsdl:types tag in a wsdl */
if (types != null) {
List extensibleElements = types.getExtensibilityElements();
Schema schemaExtension;
Object extensionObject;
XmlSchema xmlSchema;
XmlSchemaCollection xmlSchemaCollection;
wsdlDocumentBaseURI = wsdlDocumentBaseURI.substring(0, wsdlDocumentBaseURI.lastIndexOf("/") + 1);
for (Object extensibleElement : extensibleElements) {
extensionObject = extensibleElement;
if (extensionObject instanceof Schema) {
schemaExtension = (Schema)extensionObject;
xmlSchemaCollection = new XmlSchemaCollection();
/* setting base URI in the collection to load relative schemas */
xmlSchemaCollection.setBaseUri(wsdlDocumentBaseURI);
xmlSchema = xmlSchemaCollection.read(schemaExtension.getElement());
evaluateSchemasRecursively(xmlSchema, dependencies, true, false);
}
}
}
}
private void evaluateSchemasRecursively(
XmlSchema xmlSchema,
ArrayList<String> dependencies,
boolean isWSDLInlineSchema, boolean isMasterSchema) throws RegistryException {
// first process the imports and includes
XmlSchemaObjectCollection includes = xmlSchema.getIncludes();
SchemaInfo schemaInfo = new SchemaInfo();
schemaInfo.setMasterSchema(isMasterSchema);
// set this as an visited schema to stop infinite traversal
visitedSchemas.add(xmlSchema.getSourceURI());
if (includes != null) {
Object externalComponent;
XmlSchemaExternal xmlSchemaExternal;
XmlSchema innerSchema;
for (Iterator iter = includes.getIterator(); iter.hasNext();) {
externalComponent = iter.next();
if (externalComponent instanceof XmlSchemaExternal) {
xmlSchemaExternal = (XmlSchemaExternal)externalComponent;
innerSchema = xmlSchemaExternal.getSchema();
if (innerSchema != null) {
String sourceURI = innerSchema.getSourceURI();
if (isWSDLInlineSchema) {
dependencies.add(sourceURI);
}
else {
schemaInfo.getSchemaDependencies().add(sourceURI);
}
if (!visitedSchemas.contains(sourceURI)) {
evaluateSchemasRecursively(
innerSchema,
null, /* passing null is safe since we are passing isWSDLSchema = false */
false, false); /* ignore inline schema and proceed with included ones */
}
}
}
}
}
if (!isWSDLInlineSchema) {
// after processing includes and imports save the xml schema
String sourceURI = xmlSchema.getSourceURI();
String fileNameToSave = null;
if (isMasterSchema) {
fileNameToSave = extractResourceFromURL(resourceName, ".xsd");
} else {
fileNameToSave = extractResourceFromURL(sourceURI.substring(sourceURI.lastIndexOf(RegistryConstants.PATH_SEPARATOR) + 1), ".xsd");
}
fileNameToSave = fileNameToSave.replace("?xsd=", ".");
while (processedSchemas.contains(fileNameToSave)) {
fileNameToSave = fileNameToSave.substring(0, fileNameToSave.indexOf(".")) + ++i + ".xsd";
}
// add this entry to the processed schema map
processedSchemas.add(fileNameToSave);
//schemaInfo.setProposedRegistryURL(fileNameToSave);
schemaInfo.setProposedResourceName(fileNameToSave);
schemaInfo.setSchema((xmlSchema));
schemaInfo.setOriginalURL(sourceURI);
schemas.put(getAbsoluteSchemaURL(sourceURI), schemaInfo);
}
}
public String getSchemaRegistryPath(String parentRegistryPath, String sourceURL) throws RegistryException {
SchemaInfo schemaInfo = schemas.get(getAbsoluteSchemaURL(sourceURL));
if (schemaInfo != null) {
//return WSDLUtil.getLocationPrefix(parentRegistryPath) + schemaInfo.getProposedRegistryURL();
return WSDLUtil.computeRelativePathWithVersion(parentRegistryPath, schemaInfo.getProposedRegistryURL(), systemRegistry);
}
return null;
}
public String getSchemaAssociationPath(String sourceURL) {
SchemaInfo schemaInfo = schemas.get(sourceURL);
if (schemaInfo != null) {
String proposedRegistryURL = schemaInfo.getProposedRegistryURL();
return proposedRegistryURL.replaceAll("\\.\\./", "");
}
return null;
}
/**
* For each schema found in schemas, change corresponding schemaInfo's ProposedRegistryURL based on
* commonSchemaLocation and mangled targetNamespace
* @param commonSchemaLocation the location to store schemas
* @throws RegistryException if the operation failed.
*/
private void updateSchemaPaths(String commonSchemaLocation) throws RegistryException {
/* i.e. ROOT/commonSchemaLocation */
if (!systemRegistry.resourceExists(commonSchemaLocation)) {
systemRegistry.put(commonSchemaLocation, systemRegistry.newCollection());
}
for (SchemaInfo schemaInfo: schemas.values()) {
XmlSchema schema = schemaInfo.getSchema();
String targetNamespace = schema.getTargetNamespace();
if ((targetNamespace == null) || (targetNamespace == "")) {
targetNamespace = "unqualified";
}
String schemaLocation = (commonSchemaLocation +
CommonUtil.derivePathFragmentFromNamespace(targetNamespace)).replace("//", "/");
schemaLocation += schemaInfo.getProposedResourceName();
schemaInfo.setProposedRegistryURL(schemaLocation);
}
}
/**
* Update the schema's internal import location according to the new registry URLs.
* Furthermore, fill the associations arraylist according to the detectored associations.
*/
private void updateSchemaInternalsAndAssociations() throws RegistryException {
for (SchemaInfo schemaInfo: schemas.values()) {
XmlSchema schema = schemaInfo.getSchema();
XmlSchemaObjectCollection includes = schema.getIncludes();
if (includes != null) {
for (Iterator iter = includes.getIterator(); iter.hasNext();) {
Object externalComponent = iter.next();
if (externalComponent instanceof XmlSchemaExternal) {
XmlSchemaExternal xmlSchemaExternal = (XmlSchemaExternal)externalComponent;
XmlSchema schema1 = xmlSchemaExternal.getSchema();
if (schema1 != null) {
String sourceURI = getAbsoluteSchemaURL(schema1.getSourceURI());
if (schemas.containsKey(sourceURI)) {
SchemaInfo info = schemas.get(sourceURI);
String relativeSchemaPath = WSDLUtil.computeRelativePathWithVersion(schemaInfo.getProposedRegistryURL(), info.getProposedRegistryURL(), registry);
xmlSchemaExternal.setSchemaLocation(relativeSchemaPath);
}
}
}
}
}
// creating associations
for(String associatedTo : schemaInfo.getSchemaDependencies()) {
SchemaInfo schemaInfoAssociated = schemas.get(associatedTo);
if (schemaInfoAssociated != null) {
associations.add(new Association(schemaInfo.getProposedRegistryURL(),
schemaInfoAssociated.getProposedRegistryURL(),
CommonConstants.DEPENDS));
associations.add(new Association(schemaInfoAssociated.getProposedRegistryURL(),
schemaInfo.getProposedRegistryURL(),
CommonConstants.USED_BY));
}
}
}
}
public String saveSchemasToRegistry(String commonSchemaLocation, String symlinkLocation,
Resource metaResource) throws RegistryException {
updateSchemaPaths(commonSchemaLocation);
updateSchemaInternalsAndAssociations();
String path = saveSchemaToRegistry(null, symlinkLocation, metaResource);
persistAssociations();
return path;
}
@SuppressWarnings("unchecked")
private String saveSchemaToRegistry(String resourcePath, String symlinkLocation,
Resource metaResource) throws RegistryException {
String path = resourcePath;
for (SchemaInfo schemaInfo: schemas.values()) {
XmlSchema schema = schemaInfo.getSchema();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
schema.write(byteArrayOutputStream);
byte[] xsdContent = byteArrayOutputStream.toByteArray();
String schemaPath = schemaInfo.getProposedRegistryURL();
Resource xsdResource;
if (metaResource != null && registry.resourceExists(schemaPath)) {
xsdResource = registry.get(schemaPath);
} else {
xsdResource = new ResourceImpl();
if (metaResource != null) {
Properties properties = metaResource.getProperties();
if (properties != null) {
List<String> linkProperties = Arrays.asList(
RegistryConstants.REGISTRY_LINK,
RegistryConstants.REGISTRY_USER,
RegistryConstants.REGISTRY_MOUNT,
RegistryConstants.REGISTRY_AUTHOR,
RegistryConstants.REGISTRY_MOUNT_POINT,
RegistryConstants.REGISTRY_TARGET_POINT,
RegistryConstants.REGISTRY_ACTUAL_PATH,
RegistryConstants.REGISTRY_REAL_PATH);
for (Map.Entry<Object, Object> e : properties.entrySet()) {
String key = (String) e.getKey();
if (!linkProperties.contains(key)) {
xsdResource.setProperty(key, (List<String>) e.getValue());
}
}
}
}
}
xsdResource.setMediaType("application/x-xsd+xml");
xsdResource.setContent(xsdContent);
String targetNamespace = schema.getTargetNamespace();
xsdResource.addProperty("targetNamespace", targetNamespace);
if (schemaInfo.isMasterSchema() && validationInfo != null) {
ArrayList<String> messages = validationInfo.getValidationMessages();
if (messages.size() > 0) {
xsdResource.setProperty(SCHEMA_STATUS, WSDLUtils.INVALID);
} else {
xsdResource.setProperty(SCHEMA_STATUS, WSDLUtils.VALID);
}
int i = 1;
for (String message : messages) {
if (message == null) {
continue;
}
if (message.length() > 1000) {
message = message.substring(0, 997) + "...";
}
xsdResource.setProperty(SCHEMA_VALIDATION_MESSAGE + i, message);
i++;
}
}
boolean newSchemaUpload = !registry.resourceExists(schemaPath);
saveToRepositorySafely(schemaPath, xsdResource);
if (symlinkLocation == null && resourcePath != null &&
!(resourcePath.equals("/") || resourcePath.equals(schemaPath)
|| resourcePath.equals(""))) {
symlinkLocation = RegistryUtils.getParentPath(resourcePath);
}
if (schemaInfo.isMasterSchema() && symlinkLocation != null) {
if (registry.resourceExists(symlinkLocation)) {
Resource resource = registry.get(symlinkLocation);
if (resource != null) {
String isLink = resource.getProperty("registry.link");
String mountPoint = resource.getProperty("registry.mountpoint");
String targetPoint = resource.getProperty("registry.targetpoint");
String actualPath = resource.getProperty("registry.actualpath");
if (isLink != null && mountPoint != null && targetPoint != null) {
// symlinkLocation = symlinkLocation.replace(mountPoint, targetPoint);
symlinkLocation = actualPath + RegistryConstants.PATH_SEPARATOR;
}
}
}
// 1. New resource: resourcePath = /foo, schemaPath = /ns/name.xsd, symlinkPath = /foo, resourceExist = false, resourceIsSymLink = false, createSymlink = true. DoWork = true
// 2. New resource, existing symlink: resourcePath = /foo, xsdPath = /ns/name.xsd, symlinkPath = /foo, resourceExist = false, resourceIsSymLink = true, createSymlink = false
// 3. Edit from symlink: resourcePath = /foo, schemaPath = /ns/name.xsd, symlinkPath = /foo, resourceExist = true, resourceIsSymLink = true, createSymlink = false,
// 4. Edit from resource: resourcePath = /ns/name.xsd, schemaPath = /ns/name.xsd, symlinkPath = /ns/name.xsd, resourceExist = true, resourceIsSymLink = false, createSymlink = false,
// 5. Edit from resource, change ns: resourcePath = /ns/name.xsd, schemaPath = /ns2/name.xsd, symlinkPath = /ns/name.xsd, resourceExist = true, resourceIsSymLink = false, createSymlink = false, deleteResource = true. DoWork = true
// 6. Edit from symlink, change ns: resourcePath = /ns/name.xsd, schemaPath = /ns2/name.xsd, symlinkPath = /ns/name.xsd, resourceExist = true, resourceIsSymLink = true, createSymlink = delete and add, deleteResource = true. DoWork = true
if (!symlinkLocation.endsWith(RegistryConstants.PATH_SEPARATOR)) {
symlinkLocation = symlinkLocation + RegistryConstants.PATH_SEPARATOR;
}
String symlinkPath = symlinkLocation + resourceName;
if (!registry.resourceExists(symlinkPath)) {
registry.createLink(symlinkPath, schemaPath);
} else if (newSchemaUpload) {
if (registry.get(symlinkPath).getProperty(RegistryConstants.REGISTRY_LINK) != null) {
String actualPath = registry.get(symlinkPath).getProperty(RegistryConstants.REGISTRY_ACTUAL_PATH);
if (!schemaPath.equals(actualPath)) {
if (actualPath != null) {
registry.delete(actualPath);
}
registry.removeLink(symlinkPath);
registry.createLink(symlinkPath, schemaPath);
}
} else {
registry.delete(resourcePath);
}
}
}
if (schemaInfo.isMasterSchema()) {
path = schemaPath;
}
}
return path;
}
/**
* Save associations to the registry if they do not exist.
* Execution time could be improved if registry provides a better way to check existing associations.
*
* @throws RegistryException
*/
private void persistAssociations() throws RegistryException {
// until registry provides a functionality to check existing associations, this method will consume a LOT of time
for (Association association: associations) {
boolean isAssociationExist = false;
Association[] existingAssociations = registry.getAllAssociations(association.getSourcePath());
if (existingAssociations != null) {
for (Association currentAssociation: existingAssociations) {
if (currentAssociation.getDestinationPath().equals(association.getDestinationPath()) &&
currentAssociation.getAssociationType().equals(association.getAssociationType())) {
isAssociationExist = true;
break;
}
}
}
if (!isAssociationExist) {
registry.addAssociation(association.getSourcePath(),
association.getDestinationPath(),
association.getAssociationType());
}
}
}
/**
* Saves the resource iff the resource is not already existing in the repository
* @param path: resource path
* @param resource: resource object
* @throws RegistryException
*/
private void saveToRepositorySafely(String path, Resource resource)
throws RegistryException {
String schemaId = resource.getProperty(CommonConstants.ARTIFACT_ID_PROP_KEY);
if (schemaId == null) {
// generate a service id
schemaId = UUID.randomUUID().toString();
resource.setProperty(CommonConstants.ARTIFACT_ID_PROP_KEY, schemaId);
}
if (systemRegistry != null) {
CommonUtil.addGovernanceArtifactEntryWithAbsoluteValues(systemRegistry, schemaId, path);
}
if (!registry.resourceExists(path)) {
registry.put(path, resource);
} else {
log.debug("A Resource already exists at given location. Overwriting resource content.");
registry.put(path, resource);
}
// if (!(resource instanceof Collection) &&
// ((ResourceImpl) resource).isVersionableChange()) {
// registry.createVersion(path);
// }
String relativeArtifactPath = RegistryUtils.getRelativePath(registry.getRegistryContext(), path);
// adn then get the relative path to the GOVERNANCE_BASE_PATH
relativeArtifactPath = RegistryUtils.getRelativePathToOriginal(relativeArtifactPath,
RegistryConstants.GOVERNANCE_REGISTRY_BASE_PATH);
((ResourceImpl)resource).setPath(relativeArtifactPath);
}
private String extractResourceFromURL(String wsdlURL, String suffix) {
String resourceName = wsdlURL;
if (wsdlURL.indexOf("?") > 0) {
resourceName = wsdlURL.substring(0, wsdlURL.indexOf("?")) + suffix;
} else if (wsdlURL.indexOf(".") > 0) {
resourceName = wsdlURL.substring(0, wsdlURL.lastIndexOf(".")) + suffix;
} else if (!wsdlURL.endsWith(suffix)) {
resourceName = wsdlURL + suffix;
}
return resourceName;
}
private String getAbsoluteSchemaURL(String schemaLocation) throws RegistryException {
if (schemaLocation != null && baseURI != null) {
try {
URI uri = new URI(baseURI);
URI absoluteURI = uri.resolve(schemaLocation);
return absoluteURI.toString();
} catch (URISyntaxException e) {
throw new RegistryException(e.getMessage(), e);
}
}
return schemaLocation;
}
}