/*
* Copyright (c) 2015, 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 com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
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.wso2.carbon.context.CarbonContext;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.registry.core.RegistryConstants;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.ResourceImpl;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
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 javax.xml.stream.XMLStreamException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* This class contains methods to read swagger documents from a given input stream and parse the swagger document in to
* a JSON object and save the document in to the registry.
*
* This class will be initialized from the {@link org.wso2.carbon.registry.extensions.handlers.SwaggerMediaTypeHandler}
* class when a resource that has a media type of application+swagger+json has to be processed. This class will invoke
* necessary methods to create a REST Service from the imported swagger definition.
*
* @see org.wso2.carbon.registry.extensions.handlers.SwaggerMediaTypeHandler
* @see org.wso2.carbon.registry.extensions.handlers.utils.RESTServiceUtils
*/
public class SwaggerProcessor {
private static final Log log = LogFactory.getLog(SwaggerProcessor.class);
private static final String DEFAULT_TRANSPORT = "http://";
private static final String DEFAULT_BASE_PATH = "/";
private RequestContext requestContext;
private Registry registry;
private JsonParser parser;
private String swaggerResourcesPath;
private String documentVersion;
private String endpointUrl;
private OMElement restServiceElement = null;
private OMElement endpointElement = null;
private String endpointLocation;
private boolean createRestServiceArtifact;
public SwaggerProcessor(RequestContext requestContext, boolean createRestServiceArtifact) {
this.parser = new JsonParser();
this.requestContext = requestContext;
this.registry = requestContext.getRegistry();
this.createRestServiceArtifact = createRestServiceArtifact;
}
/**
* @return createRestServiceArtifact
*/
public boolean isCreateRestServiceArtifact() {
return createRestServiceArtifact;
}
/**
* @param createRestServiceArtifact boolean to set createRestServiceArtifact
*/
public void setCreateRestServiceArtifact(boolean createRestServiceArtifact) {
this.createRestServiceArtifact = createRestServiceArtifact;
}
/**
* Saves the swagger file as a registry artifact.
*
* @param inputStream input stream to read content.
* @param commonLocation root location of the swagger artifacts.
* @param sourceUrl source URL.
* @return swagger resource path.
* @throws RegistryException If a failure occurs when adding the swagger to registry.
*/
public String processSwagger(InputStream inputStream, String commonLocation, String sourceUrl)
throws RegistryException {
//create a collection if not exists.
createCollection(commonLocation);
//Reading resource content and content details.
ByteArrayOutputStream swaggerContentStream = CommonUtil.readSourceContent(inputStream);
JsonObject swaggerDocObject = getSwaggerObject(swaggerContentStream.toString());
String swaggerVersion = getSwaggerVersion(swaggerDocObject);
documentVersion = requestContext.getResource().getProperty(RegistryConstants.VERSION_PARAMETER_NAME);
if (documentVersion == null) {
documentVersion = CommonConstants.SWAGGER_DOC_VERSION_DEFAULT_VALUE;
requestContext.getResource().setProperty(RegistryConstants.VERSION_PARAMETER_NAME, documentVersion);
}
String swaggerResourcePath = getSwaggerDocumentPath(commonLocation, swaggerDocObject);
/*
Switches from the swagger version and process document adding process and the REST Service creation process
using the relevant documents.
*/
if (SwaggerConstants.SWAGGER_VERSION_12.equals(swaggerVersion)) {
if (addSwaggerDocumentToRegistry(swaggerContentStream, swaggerResourcePath, documentVersion)) {
List<JsonObject> resourceObjects = addResourceDocsToRegistry(swaggerDocObject, sourceUrl,
swaggerResourcePath);
if (isCreateRestServiceArtifact()) {
restServiceElement = (resourceObjects != null) ?
RESTServiceUtils.createRestServiceArtifact(swaggerDocObject, swaggerVersion, endpointUrl,
resourceObjects, swaggerResourcePath, documentVersion) :
null;
}
} else {
return null;
}
} else if (SwaggerConstants.SWAGGER_VERSION_2.equals(swaggerVersion)) {
if (addSwaggerDocumentToRegistry(swaggerContentStream, swaggerResourcePath, documentVersion)) {
createEndpointElement(swaggerDocObject, swaggerVersion);
if (isCreateRestServiceArtifact()) {
restServiceElement = RESTServiceUtils
.createRestServiceArtifact(swaggerDocObject, swaggerVersion, endpointUrl, null,
swaggerResourcePath, documentVersion);
}
} else {
return null;
}
}
/*
If REST Service content is not empty and createRestServiceArtifact is true,
saves the REST service and adds the relevant associations.
*/
if(isCreateRestServiceArtifact()) {
if (restServiceElement != null) {
String servicePath = RESTServiceUtils.addServiceToRegistry(requestContext, restServiceElement);
registry.addAssociation(servicePath, swaggerResourcePath, CommonConstants.DEPENDS);
registry.addAssociation(swaggerResourcePath, servicePath, CommonConstants.USED_BY);
saveEndpointElement(servicePath);
} else {
log.warn("Service content is null. Cannot create the REST Service artifact.");
}
}
CommonUtil.closeOutputStream(swaggerContentStream);
return swaggerResourcePath;
}
/**
* Save endpoint element to the registry.
*
* @param servicePath service path.
* @throws RegistryException If fails to save the endpoint.
*/
public void saveEndpointElement(String servicePath) throws RegistryException {
String endpointPath;
if (StringUtils.isNotBlank(endpointUrl)) {
EndpointUtils.addEndpointToService(requestContext.getRegistry(), servicePath, endpointUrl, "");
endpointPath = RESTServiceUtils.addEndpointToRegistry(requestContext, endpointElement, endpointLocation);
CommonUtil.addDependency(registry, servicePath, endpointPath);
}
}
/**
* Saves a swagger document in the registry.
*
* @param contentStream resource content.
* @param path resource path.
* @param documentVersion version of the swagger document.
* @throws RegistryException If fails to add the swagger document to registry.
*/
private boolean addSwaggerDocumentToRegistry(ByteArrayOutputStream contentStream, String path, String documentVersion)
throws RegistryException {
Resource resource;
/*
Checks if a resource is already exists in the given path.
If exists,
Compare resource contents and if updated, updates the document, if not skip the updating process
If not exists,
Creates a new resource and add to the resource path.
*/
if (registry.resourceExists(path)) {
resource = registry.get(path);
Object resourceContentObj = resource.getContent();
String resourceContent;
if (resourceContentObj instanceof String) {
resourceContent = (String) resourceContentObj;
resource.setContent(RegistryUtils.encodeString(resourceContent));
} else if (resourceContentObj instanceof byte[]) {
resourceContent = RegistryUtils.decodeBytes((byte[]) resourceContentObj);
} else {
throw new RegistryException(CommonConstants.INVALID_CONTENT);
}
if (resourceContent.equals(contentStream.toString())) {
if (log.isDebugEnabled()) {
log.debug("Old content is same as the new content. Skipping the put action.");
}
return true;
}
} else {
//If a resource does not exist in the given path.
resource = new ResourceImpl();
}
String resourceId = (resource.getUUID() == null) ? UUID.randomUUID().toString() : resource.getUUID();
resource.setUUID(resourceId);
resource.setMediaType(CommonConstants.SWAGGER_MEDIA_TYPE);
resource.setContent(contentStream.toByteArray());
resource.addProperty(RegistryConstants.VERSION_PARAMETER_NAME, documentVersion);
CommonUtil.copyProperties(this.requestContext.getResource(), resource);
registry.put(path, resource);
return true;
}
/**
* Creates a collection in the given common location.
*
* @param commonLocation location to create the collection.
* @throws RegistryException If fails to create a collection at given location.
*/
private void createCollection(String commonLocation) throws RegistryException {
Registry systemRegistry = CommonUtil.getUnchrootedSystemRegistry(requestContext);
//Creating a collection if not exists.
if (!systemRegistry.resourceExists(commonLocation)) {
systemRegistry.put(commonLocation, systemRegistry.newCollection());
}
}
/**
* Adds swagger 1.2 api resource documents to registry and returns a list of resource documents as JSON objects.
*
* @param swaggerDocObject swagger document JSON object.
* @param sourceUrl source url of the swagger document.
* @param swaggerDocPath swagger document path. (path of the registry)
* @return List of api resources.
* @throws RegistryException If fails to import or save resource docs to the registry.
*/
private List<JsonObject> addResourceDocsToRegistry(JsonObject swaggerDocObject, String sourceUrl,
String swaggerDocPath) throws RegistryException {
if (sourceUrl == null) {
log.debug(CommonConstants.EMPTY_URL);
log.warn("Resource paths cannot be read. Creating the REST service might fail.");
return null;
} else if (sourceUrl.startsWith("file")) {
sourceUrl = sourceUrl.substring(0,sourceUrl.lastIndexOf("/"));
}
List<JsonObject> resourceObjects = new ArrayList<>();
//Adding Resource documents to registry.
JsonArray pathResources = swaggerDocObject.get(SwaggerConstants.APIS).getAsJsonArray();
ByteArrayOutputStream resourceContentStream = null;
InputStream resourceInputStream = null;
String path;
/*
Loops through apis array of the swagger 1.2 api-doc and reads all the resource documents and saves them in to
the registry.
*/
for (JsonElement pathResource : pathResources) {
JsonObject resourceObj = pathResource.getAsJsonObject();
path = resourceObj.get(SwaggerConstants.PATH).getAsString();
try {
resourceInputStream = new URL(sourceUrl + path).openStream();
} catch (IOException e) {
throw new RegistryException("The URL " + sourceUrl + path + " is incorrect.", e);
}
resourceContentStream = CommonUtil.readSourceContent(resourceInputStream);
JsonObject resourceObject = parser.parse(resourceContentStream.toString()).getAsJsonObject();
resourceObjects.add(resourceObject);
if (endpointElement == null) {
createEndpointElement(resourceObject, SwaggerConstants.SWAGGER_VERSION_12);
}
//path = swaggerResourcesPath + path;
path = path.replace("/","");
path = CommonUtil.replaceExpressionOfPath(swaggerResourcesPath, "name", path);
path = RegistryUtils.getAbsolutePath(registry.getRegistryContext(),path);
//Save Resource document to registry
if(addSwaggerDocumentToRegistry(resourceContentStream, path, documentVersion)) {
//Adding an dependency to API_DOC
registry.addAssociation(swaggerDocPath, path, CommonConstants.DEPENDS);
}
}
CommonUtil.closeOutputStream(resourceContentStream);
CommonUtil.closeInputStream(resourceInputStream);
return resourceObjects;
}
/**
* Generates the service endpoint element from the swagger object.
*
* @param swaggerObject swagger document object.
* @param swaggerVersion swagger version.
*/
private void createEndpointElement(JsonObject swaggerObject, String swaggerVersion) throws RegistryException {
/*
Extracting endpoint url from the swagger document.
*/
if (SwaggerConstants.SWAGGER_VERSION_12.equals(swaggerVersion)) {
JsonElement endpointUrlElement = swaggerObject.get(SwaggerConstants.BASE_PATH);
if (endpointUrlElement == null) {
log.warn("Endpoint url is not specified in the swagger document. Endpoint creation might fail. ");
return;
} else {
endpointUrl = endpointUrlElement.getAsString();
}
} else if (SwaggerConstants.SWAGGER_VERSION_2.equals(swaggerVersion)) {
JsonElement transportsElement = swaggerObject.get(SwaggerConstants.SCHEMES);
JsonArray transports = (transportsElement != null) ? transportsElement.getAsJsonArray() : null;
String transport = (transports != null) ? transports.get(0).getAsString() + "://" : DEFAULT_TRANSPORT;
JsonElement hostElement = swaggerObject.get(SwaggerConstants.HOST);
String host = (hostElement != null) ? hostElement.getAsString() : null;
if (host == null) {
log.warn("Endpoint(host) url is not specified in the swagger document. "
+ "The host serving the documentation is to be used(including the port) as endpoint host");
if(requestContext.getSourceURL() != null) {
URL sourceURL = null;
try {
sourceURL = new URL(requestContext.getSourceURL());
} catch (MalformedURLException e) {
throw new RegistryException("Error in parsing the source URL. ", e);
}
host = sourceURL.getAuthority();
}
}
if (host == null) {
log.warn("Can't derive the endpoint(host) url when uploading swagger from file. "
+ "Endpoint creation might fail. ");
return;
}
JsonElement basePathElement = swaggerObject.get(SwaggerConstants.BASE_PATH);
String basePath = (basePathElement != null) ? basePathElement.getAsString() : DEFAULT_BASE_PATH;
endpointUrl = transport + host + basePath;
}
/*
Creating endpoint artifact
*/
OMFactory factory = OMAbstractFactory.getOMFactory();
endpointLocation = EndpointUtils.deriveEndpointFromUrl(endpointUrl);
String endpointName = EndpointUtils.deriveEndpointNameWithNamespaceFromUrl(endpointUrl);
String endpointContent = EndpointUtils
.getEndpointContentWithOverview(endpointUrl, endpointLocation, endpointName, documentVersion);
try {
endpointElement = AXIOMUtil.stringToOM(factory, endpointContent);
} catch (XMLStreamException e) {
throw new RegistryException("Error in creating the endpoint element. ", e);
}
}
/**
* Configures the swagger resource path form its content and returns the swagger document path.
*
* @param rootLocation root location of the swagger files.
* @param content swagger content.
* @return Common resource path.
*/
private String getSwaggerDocumentPath(String rootLocation, JsonObject content) throws RegistryException {
String swaggerDocPath = requestContext.getResourcePath().getPath();
String swaggerDocName = swaggerDocPath.substring(swaggerDocPath.lastIndexOf(RegistryConstants.PATH_SEPARATOR) + 1);
JsonElement infoElement = content.get(SwaggerConstants.INFO);
JsonObject infoObject = (infoElement != null) ? infoElement.getAsJsonObject() : null;
if (infoObject == null || infoElement.isJsonNull()) {
throw new RegistryException("Invalid swagger document.");
}
String serviceName = infoObject.get(SwaggerConstants.TITLE).getAsString().replaceAll("\\s", "");
String serviceProvider = CarbonContext.getThreadLocalCarbonContext().getUsername();
swaggerResourcesPath = rootLocation + serviceProvider + RegistryConstants.PATH_SEPARATOR + serviceName +
RegistryConstants.PATH_SEPARATOR + documentVersion;
String pathExpression = getSwaggerRegistryPath(swaggerDocName, serviceProvider);
return RegistryUtils.getAbsolutePath(registry.getRegistryContext(),pathExpression);
}
private String getSwaggerRegistryPath(String swaggerDocName, String serviceProvider) {
String pathExpression = Utils.getRxtService().getStoragePath(CommonConstants.SWAGGER_MEDIA_TYPE);
pathExpression = CommonUtil.getPathFromPathExpression(pathExpression,
requestContext.getResource().getProperties(), null);
pathExpression = CommonUtil.replaceExpressionOfPath(pathExpression, "provider", serviceProvider);
swaggerResourcesPath = pathExpression;
pathExpression = CommonUtil.replaceExpressionOfPath(pathExpression, "name", swaggerDocName);
String swaggerPath = pathExpression;
/**
* 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))) {
swaggerPath = CommonUtil.getRegistryPath(requestContext.getRegistry().getRegistryContext(), pathExpression);
if (log.isDebugEnabled()) {
log.debug("Saving current session local paths, key: " + swaggerPath + " | value: " + pathExpression);
}
CurrentSession.getLocalPathMap().put(swaggerPath, pathExpression);
}
return swaggerPath;
}
/**
* Parses the swagger content and return as a JsonObject
*
* @param swaggerContent content as a String.
* @return Swagger document as a JSON Object.
* @throws RegistryException If fails to parse the swagger document.
*/
private JsonObject getSwaggerObject(String swaggerContent) throws RegistryException {
JsonElement swaggerElement = parser.parse(swaggerContent);
if (swaggerElement == null || swaggerElement.isJsonNull()) {
throw new RegistryException("Unexpected error occurred when parsing the swagger content.");
} else {
return swaggerElement.getAsJsonObject();
}
}
/**
* Returns swagger version
*
* @param swaggerDocObject swagger JSON.
* @return Swagger version.
* @throws RegistryException If swagger version is unsupported.
*/
private String getSwaggerVersion(JsonObject swaggerDocObject) throws RegistryException {
//Getting the swagger version
JsonElement swaggerVersionElement = swaggerDocObject.get(SwaggerConstants.SWAGGER_VERSION_KEY);
swaggerVersionElement =
(swaggerVersionElement == null) ? swaggerDocObject.get(SwaggerConstants.SWAGGER2_VERSION_KEY) :
swaggerVersionElement;
if (swaggerVersionElement == null) {
throw new RegistryException("Unsupported swagger version.");
}
return swaggerVersionElement.getAsString();
}
}