/*
* Copyright (c) 2014, 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.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axiom.om.util.AXIOMUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xerces.xni.parser.XMLInputSource;
import org.jvnet.ws.wadl.ast.InvalidWADLException;
import org.jvnet.ws.wadl.ast.WadlAstBuilder;
import org.jvnet.ws.wadl.util.MessageListener;
import org.w3c.dom.Element;
import org.wso2.carbon.registry.core.*;
import org.wso2.carbon.registry.core.config.RegistryContext;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.jdbc.Repository;
import org.wso2.carbon.registry.core.jdbc.VersionRepository;
import org.wso2.carbon.registry.core.jdbc.handlers.RequestContext;
import org.wso2.carbon.registry.core.session.CurrentSession;
import org.wso2.carbon.registry.core.utils.RegistryUtils;
import org.wso2.carbon.registry.extensions.services.Utils;
import org.wso2.carbon.registry.extensions.utils.CommonConstants;
import org.wso2.carbon.registry.extensions.utils.CommonUtil;
import org.wso2.carbon.registry.extensions.utils.WSDLValidationInfo;
import org.xml.sax.InputSource;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.ConnectException;
import java.net.URI;
import java.net.URL;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
public class WADLProcessor {
private static final Log log = LogFactory.getLog(WADLProcessor.class);
private static final String WADL_EXTENSION = ".wadl";
private String wadlMediaType = "application/wadl+xml";
private String xsdMediaType = "application/xsd+xml";
private static String commonWADLLocation = "/wadls/";
private static String commonSchemaLocation = "/schemas/";
private Registry registry;
private Repository repository;
private VersionRepository versionRepository;
private boolean createService = true;
private List<String> importedSchemas = new LinkedList<String>();
private OMElement wadlElement;
public WADLProcessor(RequestContext requestContext) {
registry = requestContext.getRegistry();
repository = requestContext.getRepository();
versionRepository = requestContext.getVersionRepository();
}
public boolean getCreateService() {
return createService;
}
public void setCreateService(boolean createService) {
this.createService = createService;
}
public static String getCommonSchemaLocation() {
return commonSchemaLocation;
}
public static void setCommonSchemaLocation(String commonSchemaLocation) {
WADLProcessor.commonSchemaLocation = commonSchemaLocation;
}
public static String getCommonWADLLocation() {
return commonWADLLocation;
}
public static void setCommonWADLLocation(String commonWADLLocation) {
WADLProcessor.commonWADLLocation = commonWADLLocation;
}
public String addWadlToRegistry(RequestContext requestContext, Resource resource,
String resourcePath,boolean skipValidation)
throws RegistryException {
String wadlName = RegistryUtils.getResourceName(resourcePath);
String version = requestContext.getResource().getProperty(RegistryConstants.VERSION_PARAMETER_NAME);
if (version == null){
version = CommonConstants.WADL_VERSION_DEFAULT_VALUE;
requestContext.getResource().setProperty(RegistryConstants.VERSION_PARAMETER_NAME, version);
}
String wadlContent;
Object resourceContent = resource.getContent();
if (resourceContent instanceof String) {
wadlContent = (String) resourceContent;
} else {
wadlContent = new String((byte[]) resourceContent);
}
try {
XMLStreamReader reader = XMLInputFactory.newInstance().
createXMLStreamReader(new StringReader(wadlContent));
StAXOMBuilder builder = new StAXOMBuilder(reader);
wadlElement = builder.getDocumentElement();
} catch (XMLStreamException e) {
//This exception is unexpected because the WADL already validated
String msg = "Unexpected error occured " +
"while reading the WADL at " + resourcePath + ".";
log.error(msg);
throw new RegistryException(msg, e);
}
String wadlNamespace = wadlElement.getNamespace().getNamespaceURI();
String actualPath = getWadlLocation(requestContext,wadlElement,wadlName,version);
OMElement grammarsElement = wadlElement.
getFirstChildWithName(new QName(wadlNamespace, "grammars"));
if (StringUtils.isNotBlank(requestContext.getSourceURL())) {
String uri = requestContext.getSourceURL();
if (!skipValidation) {
validateWADL(uri);
}
if (resource.getUUID() == null) {
resource.setUUID(UUID.randomUUID().toString());
}
String wadlBaseUri = uri.substring(0, uri.lastIndexOf("/") + 1);
if (grammarsElement != null) {
//This is to avoid evaluating the grammars import when building AST
grammarsElement.detach();
wadlElement.addChild(resolveImports(grammarsElement, wadlBaseUri, version, requestContext.getResource().getProperties()));
}
} else {
if (!skipValidation) {
File tempFile = null;
BufferedWriter bufferedWriter = null;
FileWriter fileWriter = null;
try {
tempFile = File.createTempFile(wadlName, null);
fileWriter = new FileWriter(tempFile);
bufferedWriter = new BufferedWriter(fileWriter);
bufferedWriter.write(wadlElement.toString());
bufferedWriter.flush();
} catch (IOException e) {
String msg = "Error occurred while reading the WADL "+ wadlName +" file";
log.error(msg, e);
throw new RegistryException(msg, e);
} finally {
if (fileWriter != null){
try {
fileWriter.close();
} catch (IOException e) {
String msg = "Error occurred while closing "+ wadlName +" file writer";
log.warn(msg, e);
}
}
if (bufferedWriter != null) {
try {
bufferedWriter.close();
} catch (IOException e) {
String msg = "Error occurred while closing WADL "+ wadlName +" file writer";
log.warn(msg, e);
}
}
}
validateWADL(tempFile.toURI().toString());
try {
delete(tempFile);
} catch (IOException e) {
String msg = "An error occurred while deleting the temporary files from local file system.";
log.warn(msg, e);
throw new RegistryException(msg, e);
}
}
if (grammarsElement != null) {
grammarsElement = resolveImports(grammarsElement, null, version, requestContext.getResource().getProperties());
wadlElement.addChild(grammarsElement);
}
}
requestContext.setResourcePath(new ResourcePath(actualPath));
if (resource.getProperty(CommonConstants.SOURCE_PROPERTY) == null){
resource.setProperty(CommonConstants.SOURCE_PROPERTY, CommonConstants.SOURCE_AUTO);
}
registry.put(actualPath, resource);
addImportAssociations(actualPath);
if(getCreateService()){
// when creating REST service for wadl, Both resources had same uuid.
// By adding new resource to request context. Inside addServiceToRegistry, it will check for uuid.
// And it will create new random UUID for REST service
Resource tempResource = new ResourceImpl();
requestContext.setResource(tempResource);
OMElement serviceElement = RESTServiceUtils.createRestServiceArtifact(wadlElement, wadlName, version,
RegistryUtils.getRelativePath(requestContext.getRegistryContext(), actualPath));
String servicePath = RESTServiceUtils.addServiceToRegistry(requestContext, serviceElement);
registry.addAssociation(servicePath, actualPath, CommonConstants.DEPENDS);
registry.addAssociation(actualPath, servicePath, CommonConstants.USED_BY);
saveEndpointElement(requestContext, servicePath, version);
}
return resource.getPath();
}
/**
* This method try to delete the temporary file,
* If it fails it will just log a warning msg.
*
* @param file
* @throws IOException
*/
private void delete(File file) throws IOException {
if (file != null && file.exists() && !file.delete()) {
log.warn("Failed to delete file/directory at path: " + file.getAbsolutePath());
}
}
public String importWADLToRegistry(RequestContext requestContext, boolean skipValidation)
throws RegistryException {
ResourcePath resourcePath = requestContext.getResourcePath();
String wadlName = RegistryUtils.getResourceName(resourcePath.getPath());
if(!wadlName.endsWith(WADL_EXTENSION)) {
wadlName += WADL_EXTENSION;
}
String version = requestContext.getResource().getProperty(RegistryConstants.VERSION_PARAMETER_NAME);
if (version == null) {
version = CommonConstants.WADL_VERSION_DEFAULT_VALUE;
requestContext.getResource().setProperty(RegistryConstants.VERSION_PARAMETER_NAME, version);
}
String uri = requestContext.getSourceURL();
if (!skipValidation) {
validateWADL(uri);
}
Registry registry = requestContext.getRegistry();
Resource resource = registry.newResource();
if (resource.getUUID() == null) {
resource.setUUID(UUID.randomUUID().toString());
}
resource.setMediaType(wadlMediaType);
resource.setProperties(requestContext.getResource().getProperties());
ByteArrayOutputStream outputStream = null;
InputStream inputStream = null;
try {
inputStream = new URL(uri).openStream();
outputStream = new ByteArrayOutputStream();
int nextChar;
while ((nextChar = inputStream.read()) != -1) {
outputStream.write(nextChar);
}
outputStream.flush();
wadlElement = AXIOMUtil.stringToOM(new String(outputStream.toByteArray()));
// to validate XML
wadlElement.toString();
} catch (Exception e) {
//This exception is unexpected because the WADL already validated
throw new RegistryException("Unexpected error occured " +
"while reading the WADL at" + uri, e);
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
String msg = "Error while closing outputStream";
log.warn(msg);
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
String msg = "Error while closing inputStream";
log.warn(msg);
}
}
}
String wadlNamespace = wadlElement.getNamespace().getNamespaceURI();
OMElement grammarsElement = wadlElement.
getFirstChildWithName(new QName(wadlNamespace, "grammars"));
String wadlBaseUri = uri.substring(0, uri.lastIndexOf("/") + 1);
if (grammarsElement != null) {
grammarsElement.detach();
wadlElement.addChild(resolveImports(grammarsElement, wadlBaseUri, version,
requestContext.getResource().getProperties()));
}
String actualPath;
// if(commonLocation != null){
// actualPath = commonLocation + namespaceSegment + version + "/" + wadlName;
// } else {
// actualPath = RegistryConstants.GOVERNANCE_REGISTRY_BASE_PATH +
// commonWADLLocation + namespaceSegment + version + "/" + wadlName;
actualPath = getWadlLocation(requestContext, wadlElement, wadlName, version);
// }
if (resource.getProperty(CommonConstants.SOURCE_PROPERTY) == null) {
resource.setProperty(CommonConstants.SOURCE_PROPERTY, CommonConstants.SOURCE_AUTO);
}
resource.setContent(wadlElement.toString());
requestContext.setResourcePath(new ResourcePath(actualPath));
registry.put(actualPath, resource);
addImportAssociations(actualPath);
if (createService) {
OMElement serviceElement = RESTServiceUtils.createRestServiceArtifact(wadlElement, wadlName, version,
RegistryUtils.getRelativePath(requestContext.getRegistryContext(), actualPath));
String servicePath = RESTServiceUtils.addServiceToRegistry(requestContext, serviceElement);
CommonUtil.addDependency(registry, servicePath, actualPath);
saveEndpointElement(requestContext, servicePath, version);
}
return actualPath;
}
/**
* Save endpoint element to the registry.
*
* @param requestContext information about the current request.
* @param servicePath service path.
* @param version service version.
* @throws RegistryException If fails to save the endpoint.
*/
public void saveEndpointElement(RequestContext requestContext, String servicePath, String version)
throws RegistryException {
String endpointPath = createEndpointElement(requestContext, wadlElement, version, servicePath);
if (StringUtils.isNotBlank(endpointPath)) {
CommonUtil.addDependency(registry, servicePath, endpointPath);
}
}
private OMElement resolveImports(OMElement grammarsElement,
String wadlBaseUri, String wadlVersion, Properties props) throws RegistryException {
String wadlNamespace = grammarsElement.getNamespace().getNamespaceURI();
Iterator<OMElement> grammarElements = grammarsElement.
getChildrenWithName(new QName(wadlNamespace, "include"));
while (grammarElements.hasNext()){
OMElement childElement = grammarElements.next();
OMAttribute refAttr = childElement.getAttribute(new QName("href"));
String importUrl = refAttr.getAttributeValue();
if(importUrl.endsWith(".xsd")) {
if(!importUrl.startsWith("http")){
if (registry.resourceExists(importUrl)) {
continue;
} else {
if (wadlBaseUri != null) {
importUrl = wadlBaseUri + importUrl;
}
}
}
String schemaPath = saveSchema(importUrl, wadlVersion, props);
importedSchemas.add(schemaPath);
refAttr.setAttributeValue(schemaPath);
childElement.addAttribute(refAttr);
}
}
return grammarsElement;
}
private void validateWADL(String uri) throws RegistryException {
WadlAstBuilder builder = new WadlAstBuilder(
new WadlAstBuilder.SchemaCallback() {
public void processSchema(InputSource is) {
try {
} finally {
if (is != null && is.getByteStream() != null) {
try {
is.getByteStream().close();
} catch (IOException e) {
String msg = "Error while closing InputSource";
log.warn(msg);
}
}
}
}
public void processSchema(String uri, Element node) {
}
},
new MessageListener() {
public void warning(String message, Throwable throwable) {
}
public void info(String message) {
}
public void error(String message, Throwable throwable) {
}
});
try {
builder.buildAst(new URI(uri));
} catch (ConnectException e) {
String msg = "Invalid WADL uri found " + uri;
throw new RegistryException(msg, e);
} catch (InvalidWADLException e){
String msg = "Invalid WADL definition found";
throw new RegistryException(msg, e);
} catch (FileNotFoundException e) {
String msg = "WADL not found";
throw new RegistryException(msg, e);
} catch (Exception e) {
String msg = "Unexpected error occured while adding WADL from " + uri;
throw new RegistryException(msg, e);
}
}
private String saveSchema(String schemaUrl, String version, Properties props) throws RegistryException {
if(schemaUrl != null){
RequestContext requestContext =
new RequestContext(registry, repository, versionRepository);
Resource local = requestContext.getRegistry().newResource();
local.setMediaType(xsdMediaType);
local.setProperty(CommonConstants.SOURCE_PROPERTY, CommonConstants.SOURCE_AUTO);
local.setProperties(props);
requestContext.setSourceURL(schemaUrl);
requestContext.setResource(local);
String xsdName = schemaUrl;
if (xsdName.lastIndexOf("/") != -1) {
xsdName = xsdName.substring(xsdName.lastIndexOf("/"));
} else {
xsdName = "/" + xsdName;
}
String path = RegistryConstants.GOVERNANCE_REGISTRY_BASE_PATH + xsdName;
requestContext.setResourcePath(new ResourcePath(path));
WSDLValidationInfo validationInfo;
try {
validationInfo = SchemaValidator.validate(new XMLInputSource(null, schemaUrl, null));
} catch (Exception e) {
throw new RegistryException("Exception occured while validating the schema" , e);
}
SchemaProcessor schemaProcessor = new SchemaProcessor(requestContext, validationInfo);
try {
return schemaProcessor.importSchemaToRegistry(requestContext, path,
getChrootedSchemaLocation(requestContext.getRegistryContext()), true, true);
} catch (RegistryException e) {
throw new RegistryException("Failed to import the schema" , e);
}
}
return null;
}
/**
* Creates endpoint element for REST service
*
* @param requestContext information about current request.
* @param wadlElement wadl document.
* @param version wadl version.
* @return Endpoint Path.
* @throws RegistryException If fails to create endpoint element.
*/
private String createEndpointElement(RequestContext requestContext, OMElement wadlElement, String version,
String servicePath) throws RegistryException {
OMNamespace wadlNamespace = wadlElement.getNamespace();
String wadlNamespaceURI = wadlNamespace.getNamespaceURI();
String wadlNamespacePrefix = wadlNamespace.getPrefix();
OMElement resourcesElement = wadlElement
.getFirstChildWithName(new QName(wadlNamespaceURI, "resources", wadlNamespacePrefix));
if (resourcesElement != null) {
String endpointUrl = resourcesElement.getAttributeValue(new QName("base"));
if (!StringUtils.isBlank(endpointUrl)) {
String endpointPath = EndpointUtils.deriveEndpointFromUrl(endpointUrl);
String endpointName = EndpointUtils.deriveEndpointNameWithNamespaceFromUrl(endpointUrl);
String endpointContent = EndpointUtils
.getEndpointContentWithOverview(endpointUrl, endpointPath, endpointName, version);
OMElement endpointElement;
EndpointUtils.addEndpointToService(requestContext.getRegistry(), servicePath, endpointUrl, "");
try {
endpointElement = AXIOMUtil.stringToOM(endpointContent);
} catch (XMLStreamException e) {
throw new RegistryException("Error in creating the endpoint element. ", e);
}
return RESTServiceUtils.addEndpointToRegistry(requestContext, endpointElement, endpointPath);
} else {
log.warn("Base path does not exist. endpoint creation may fail. ");
}
} else {
log.warn("Resources element is null. ");
}
return null;
}
private void addImportAssociations(String path) throws RegistryException {
for (String schema : importedSchemas) {
CommonUtil.addDependency(registry, path, schema);
}
}
private String getChrootedWadlLocation(RegistryContext registryContext) {
return RegistryUtils.getAbsolutePath(registryContext,
RegistryConstants.GOVERNANCE_REGISTRY_BASE_PATH + getCommonWADLLocation());
}
private String getChrootedSchemaLocation(RegistryContext registryContext) {
return RegistryUtils.getAbsolutePath(registryContext,
RegistryConstants.GOVERNANCE_REGISTRY_BASE_PATH + getCommonSchemaLocation());
}
private String getWadlLocation(RequestContext context, OMElement wadlElement, String wadlName,
String version) {
if (Utils.getRxtService() != null) {
String pathExpression = Utils.getRxtService().getStoragePath(wadlMediaType);
pathExpression = CommonUtil.replaceExpressionOfPath(pathExpression, "name", wadlName);
pathExpression = CommonUtil.getPathFromPathExpression(pathExpression,
context.getResource().getProperties(), null);
String namespace = CommonUtil.derivePathFragmentFromNamespace(
wadlElement.getNamespace().getNamespaceURI()).replace("//", "/");
namespace = namespace.replace(".", "/");
pathExpression = CommonUtil.replaceExpressionOfPath(pathExpression, "namespace", namespace);
pathExpression = pathExpression.replace("//", "/");
pathExpression = CommonUtil.replaceExpressionOfPath(pathExpression, "version", version);
String wadlPath = RegistryUtils.getAbsolutePath(context.getRegistryContext(), pathExpression.replace("//", "/"));
/**
* Fix for the REGISTRY-3052 : validation is to check the whether this invoked by ZIPWSDLMediaTypeHandler
* Setting the registry and absolute paths to current session to avoid incorrect resource path entry in REG_LOG table
*/
if (CurrentSession.getLocalPathMap() != null && !Boolean.valueOf(CurrentSession.getLocalPathMap().get(CommonConstants.ARCHIEVE_UPLOAD))) {
wadlPath = CommonUtil.getRegistryPath(context.getRegistry().getRegistryContext(), wadlPath);
CurrentSession.getLocalPathMap().remove(context.getResourcePath().getCompletePath());
if (log.isDebugEnabled()) {
log.debug("Saving current session local paths, key: " + wadlPath + " | value: " + pathExpression);
}
CurrentSession.getLocalPathMap().put(wadlPath, pathExpression);
}
return wadlPath;
} else {
String wadlNamespace = wadlElement.getNamespace().getNamespaceURI();
String namespaceSegment = CommonUtil.derivePathFragmentFromNamespace(
wadlNamespace).replace("//", "/");
String actualPath = getChrootedWadlLocation(context.getRegistryContext()) +
namespaceSegment + version + "/" + wadlName;
return actualPath;
}
}
}