/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.translator.swagger; import java.io.IOException; import java.io.InputStream; import java.sql.Blob; import java.sql.SQLException; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.teiid.core.util.ObjectConverterUtil; import org.teiid.language.Argument; import org.teiid.language.Argument.Direction; import org.teiid.language.Array; import org.teiid.language.Call; import org.teiid.language.Expression; import org.teiid.language.Literal; import org.teiid.language.SQLConstants.Tokens; import org.teiid.metadata.Column; import org.teiid.metadata.ColumnSet; import org.teiid.metadata.Procedure; import org.teiid.metadata.ProcedureParameter; import org.teiid.metadata.RestMetadataExtension; import org.teiid.metadata.RuntimeMetadata; import org.teiid.translator.DataNotAvailableException; import org.teiid.translator.ExecutionContext; import org.teiid.translator.ProcedureExecution; import org.teiid.translator.TranslatorException; import org.teiid.translator.WSConnection; import org.teiid.translator.ws.BinaryWSProcedureExecution; public class SwaggerProcedureExecution extends BaseQueryExecution implements ProcedureExecution { private Object returnValue; private SwaggerResponse response; private Class<?>[] expectedColumnTypes; private Call command; private Map<String, Object> responseHeaders; public SwaggerProcedureExecution(Call command, SwaggerExecutionFactory translator, ExecutionContext executionContext, RuntimeMetadata metadata, WSConnection connection) throws TranslatorException { super(translator, executionContext, metadata, connection); this.command = command; this.expectedColumnTypes = command.getResultSetColumnTypes(); } private ProcedureParameter getReturnParameter() { for (ProcedureParameter pp : this.command.getMetadataObject().getParameters()) { if (pp.getType() == ProcedureParameter.Type.ReturnValue) { return pp; } } return null; } private BinaryWSProcedureExecution buildWSExecution(Call obj) throws TranslatorException { Procedure procedure = obj.getMetadataObject(); String uri = procedure.getProperty(RestMetadataExtension.URI, false); String method = procedure.getProperty(RestMetadataExtension.METHOD, false); StringBuilder queryParameters = new StringBuilder(); StringBuilder formParameters = new StringBuilder(); Map<String, List<String>> headers = new HashMap<String, List<String>>(); Object payload = null; SwaggerBodyInputDocument input = null; // body payload document final List<Argument> params = obj.getArguments(); if (params != null && params.size() != 0) { Argument param = null; for (int i = 0; i < params.size(); i++) { param = params.get(i); ProcedureParameter metadata = param.getMetadataObject(); String argName = WSConnection.Util.httpURLEncode(param.getMetadataObject().getName()); if (param.getDirection() == Direction.IN || param.getDirection() == Direction.INOUT) { String in = metadata.getProperty(RestMetadataExtension.PARAMETER_TYPE, false); if (in.equalsIgnoreCase(RestMetadataExtension.ParameterType.QUERY.name())) { if (queryParameters.length() != 0) { queryParameters.append("&"); //$NON-NLS-1$ } Object value = param.getExpression(); if (value instanceof Array) { addArgumentValue(argName, (Array)value, metadata.getProperty(SwaggerMetadataProcessor.COLLECION_FORMAT, false), queryParameters); } else { String argValue = WSConnection.Util.httpURLEncode(((Literal)value).getValue().toString()); queryParameters.append(argName); queryParameters.append(Tokens.EQ); queryParameters.append(argValue); } } else if (in.equalsIgnoreCase(RestMetadataExtension.ParameterType.PATH.name())) { String argValue = WSConnection.Util.httpURLEncode(param.getArgumentValue().getValue().toString()); String regex = "\\{" + argName + "\\}"; //$NON-NLS-1$ //$NON-NLS-2$ uri = uri.replaceAll(regex, argValue); } else if (in.equalsIgnoreCase(RestMetadataExtension.ParameterType.FORM.name()) || in.equalsIgnoreCase(RestMetadataExtension.ParameterType.FORMDATA.name())) { if (formParameters.length() != 0) { formParameters.append("&"); //$NON-NLS-1$ } Object value = param.getExpression(); if (value instanceof Array) { addArgumentValue(argName, (Array)value, metadata.getProperty(SwaggerMetadataProcessor.COLLECION_FORMAT, false), formParameters); } else { formParameters.append(argName); formParameters.append(Tokens.EQ); formParameters.append(WSConnection.Util.httpURLEncode(((Literal)value).getValue().toString())); } } else if (in.equalsIgnoreCase(RestMetadataExtension.ParameterType.BODY.name())) { if (input == null) { input = new SwaggerBodyInputDocument(); } Object expr = param.getExpression(); if (expr instanceof Literal) { expr = ((Literal)expr).getValue(); } input.addArgument(param.getMetadataObject(), expr); } else if (in.equalsIgnoreCase(RestMetadataExtension.ParameterType.HEADER.name())) { String argValue = param.getArgumentValue().getValue().toString(); headers.put(argName, Arrays.asList(argValue)); } } else { throw new TranslatorException("Not supported parameter"); } } } String consumes = procedure.getProperty(RestMetadataExtension.CONSUMES, false); if (consumes == null) { consumes = "application/json"; } if (input != null) { try { SwaggerSerializer serializer = getSerializer(consumes); InputStream oos = serializer.serialize(input); payload = ObjectConverterUtil.convertToString(oos); } catch (IOException e) { throw new TranslatorException(e); } } if (payload == null && formParameters.length() > 0) { payload = formParameters.toString(); } headers.put("Content-Type", Arrays.asList(consumes)); String produces = procedure.getProperty(RestMetadataExtension.PRODUCES, false); if (produces == null) { produces = "application/json"; } headers.put("Accept", Arrays.asList(produces)); if (queryParameters.length() > 0) { uri = uri+"?"+queryParameters; } return buildInvokeHTTP(method, uri, payload, headers); } private void addArgumentValue(String argName, Array value, String collectionFormat, StringBuilder queryStr) { List<Expression> exprs = value.getExpressions(); if (collectionFormat.equalsIgnoreCase("multi")) { for (int i = 0; i< exprs.size(); i++) { if (i > 0) { queryStr.append("&"); } queryStr.append(argName); queryStr.append(Tokens.EQ); Literal l = (Literal)exprs.get(i); queryStr.append(WSConnection.Util.httpURLEncode(l.getValue().toString())); } } else { String delimiter = ","; if (collectionFormat.equalsIgnoreCase("csv")) { delimiter = ","; } else if (collectionFormat.equalsIgnoreCase("ssv")) { delimiter = " "; } else if (collectionFormat.equalsIgnoreCase("tsv")) { delimiter = "\t"; } else if (collectionFormat.equalsIgnoreCase("pipes")) { delimiter = "|"; } queryStr.append(argName); queryStr.append(Tokens.EQ); for (int i = 0; i< exprs.size(); i++) { Literal l = (Literal)exprs.get(i); if (i > 0) { queryStr.append(delimiter); } queryStr.append(WSConnection.Util.httpURLEncode(l.getValue().toString())); } } } @Override public void execute() throws TranslatorException { Procedure procedure = this.command.getMetadataObject(); BinaryWSProcedureExecution execution = buildWSExecution(this.command); execution.execute(); if (execution.getResponseCode() >= 200 && execution.getResponseCode() < 300) { this.responseHeaders = execution.getResponseHeaders(); // Success if (procedure.getResultSet() != null) { if (procedure.getResultSet().getColumns().get(0).getName().equals("return")) { // this procedure with return, but headers made this into a resultset. this.returnValue = getReturnValue(execution); } else { try { Blob blob = (Blob)execution.getOutputParameterValues().get(0); InputStream wsResponse = blob.getBinaryStream(); Object obj = execution.getResponseHeader("Content-Type"); if (obj == null) { throw new TranslatorException(SwaggerPlugin.Event.TEIID28017, SwaggerPlugin.Util.gs(SwaggerPlugin.Event.TEIID28017, "Not Defined")); } else { List<?> contentType = (List<?>)obj; SwaggerSerializer serializer = getSerializer(contentType.get(0).toString()); if (serializer == null) { throw new TranslatorException(SwaggerPlugin.Event.TEIID28017, SwaggerPlugin.Util.gs(SwaggerPlugin.Event.TEIID28017, obj.toString())); } handleResponse(procedure, wsResponse, execution.getResponseHeaders(), serializer); } } catch (SQLException e) { throw new TranslatorException(e); } } } else if (getReturnParameter() != null) { // this is scalar result this.returnValue = getReturnValue(execution); } } else if (execution.getResponseCode() == 404) { // treat as empty response. Typically that when someone uses it. } else { throw new TranslatorException(SwaggerPlugin.Event.TEIID28018, SwaggerPlugin.Util.gs(SwaggerPlugin.Event.TEIID28018, execution.getResponseCode())); } } private String getReturnValue(BinaryWSProcedureExecution execution) throws TranslatorException { try { return ObjectConverterUtil.convertToString( ((Blob)execution.getOutputParameterValues().get(0)).getBinaryStream()); } catch (IOException e) { throw new TranslatorException(e); } catch (SQLException e) { throw new TranslatorException(e); } } public static SwaggerSerializer getSerializer(String contentType) { ContentType type = ContentType.parse(contentType); if (type.isJSON()) { return new JsonSerializer(); } else if (type.isXML()) { return new XMLSerializer(); } return null; } private void handleResponse(final Procedure procedure, final InputStream payload, Map<String, Object> headers, SwaggerSerializer serializer) throws TranslatorException { this.response = new SwaggerResponse(payload, headers, serializer, isMapResponse(procedure)); } @Override public List<?> next() throws TranslatorException, DataNotAvailableException { Procedure procedure = this.command.getMetadataObject(); if (this.response != null) { Map<String, Object> row = this.response.getNext(); if (row != null) { row.putAll(this.responseHeaders); return buildRow(procedure.getResultSet().getColumns(), this.response.isMapResponse(), this.expectedColumnTypes, row); } } if (this.returnValue != null) { Map<String, Object> row = new LinkedHashMap<String, Object>(); row.put("return", SwaggerTypeManager.convertTeiidRuntimeType(this.returnValue, this.expectedColumnTypes[0])); row.putAll(this.responseHeaders); this.returnValue = null; return buildRow(procedure.getResultSet().getColumns(), false, this.expectedColumnTypes, row); } return null; } private boolean isMapResponse(Procedure procedure) { ColumnSet<Procedure> columnSet = procedure.getResultSet(); if (columnSet == null) { return false; } List<Column> columns = columnSet .getColumns(); if (columns.size() >=2 && columns.get(0).getName().equals(SwaggerMetadataProcessor.KEY_NAME) && columns.get(1).getName().equals(SwaggerMetadataProcessor.KEY_VALUE)) { return true; } return false; } @Override public List<?> getOutputParameterValues() throws TranslatorException { return Arrays.asList(this.returnValue); } @Override public void close() { } @Override public void cancel() throws TranslatorException { } private static class ContentType { private static String APPLICATION = "application"; //$NON-NLS-1$ private static String TYPE_JSON = "json"; //$NON-NLS-1$ private static String TYPE_XML = "xml"; //$NON-NLS-1$ private String major; private String subtype; ContentType(String major, String subtype) { this.major = major; this.subtype = subtype; } public boolean isJSON() { return major != null && subtype != null && major.equals(APPLICATION) && subtype.equals(TYPE_JSON); } public boolean isXML() { return major != null && subtype != null && major.equals(APPLICATION) && subtype.equals(TYPE_XML); } public static ContentType parse(String type) { int typeIndex = type.indexOf('/'); //$NON-NLS-1$ int paramIndex = type.indexOf(';'); //$NON-NLS-1$ String major = null; String subtype = null; if(typeIndex > 0) { major = type.substring(0, typeIndex); if (paramIndex > -1) { subtype = type.substring(typeIndex + 1, paramIndex); } else { subtype = type.substring(typeIndex + 1); } } return new ContentType(major, subtype); } } }