/* * 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.odata4; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; import java.sql.Blob; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.olingo.client.api.serialization.ODataDeserializer; import org.apache.olingo.client.core.serialization.AtomDeserializer; import org.apache.olingo.client.core.serialization.JsonDeserializer; import org.apache.olingo.commons.api.ex.ODataError; import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.api.http.HttpStatusCode; import org.teiid.core.TeiidException; import org.teiid.language.Argument; import org.teiid.language.Argument.Direction; import org.teiid.language.Call; import org.teiid.language.Literal; import org.teiid.logging.LogConstants; import org.teiid.logging.LogManager; import org.teiid.logging.MessageLevel; import org.teiid.metadata.AbstractMetadataRecord; import org.teiid.metadata.Column; import org.teiid.metadata.RuntimeMetadata; import org.teiid.olingo.common.ODataTypeManager; import org.teiid.translator.ExecutionContext; import org.teiid.translator.TranslatorException; import org.teiid.translator.TypeFacility; import org.teiid.translator.WSConnection; import org.teiid.translator.ws.BinaryWSProcedureExecution; public class BaseQueryExecution { protected WSConnection connection; protected ODataExecutionFactory translator; protected RuntimeMetadata metadata; protected ExecutionContext executionContext; public BaseQueryExecution(ODataExecutionFactory translator, ExecutionContext executionContext, RuntimeMetadata metadata, WSConnection connection) { this.metadata = metadata; this.executionContext = executionContext; this.translator = translator; this.connection = connection; } protected InputStream executeQuery(String method, String uri, String payload, String eTag, HttpStatusCode[] expectedStatus) throws TranslatorException { Map<String, List<String>> headers = getDefaultHeaders(); if (eTag != null) { headers.put("If-Match", Arrays.asList(eTag)); //$NON-NLS-1$ } if (payload != null) { headers.put("Content-Type", Arrays.asList( //$NON-NLS-1$ ContentType.APPLICATION_JSON.toContentTypeString())); } BinaryWSProcedureExecution execution; try { execution = invokeHTTP(method, uri, payload, headers); for (HttpStatusCode status:expectedStatus) { if (status.getStatusCode() == execution.getResponseCode()) { if (execution.getResponseCode() != HttpStatusCode.NO_CONTENT.getStatusCode() && execution.getResponseCode() != HttpStatusCode.NOT_FOUND.getStatusCode()) { Blob blob = (Blob)execution.getOutputParameterValues().get(0); return blob.getBinaryStream(); } // this is success with no-data return null; } } } catch (SQLException e) { throw new TranslatorException(e); } // throw an error throw buildError(execution); } String getHeader(BinaryWSProcedureExecution execution, String header) { Object value = execution.getResponseHeader(header); if (value instanceof List) { return (String)((List<?>)value).get(0); } return (String)value; } protected TranslatorException buildError(BinaryWSProcedureExecution execution) { // do some error handling try { Blob blob = (Blob)execution.getOutputParameterValues().get(0); if (blob != null) { boolean json = false; String contentTypeString = getHeader(execution, "Content-Type"); //$NON-NLS-1$ if (contentTypeString != null) { ContentType contentType = ContentType.parse(contentTypeString); if (contentType != null && ContentType.APPLICATION_JSON.isCompatible(contentType)) { json = true; } } ODataDeserializer parser = null; if (json) { parser = new JsonDeserializer(false); } else { //TODO: it may not be atom, it could just be xml/html parser = new AtomDeserializer(); } ODataError error = parser.toError(blob.getBinaryStream()); return new TranslatorException(ODataPlugin.Util.gs( ODataPlugin.Event.TEIID17013, execution.getResponseCode(), error.getCode(), error.getMessage(), error.getInnerError())); } return new TranslatorException(ODataPlugin.Util.gs( ODataPlugin.Event.TEIID17031, execution.getResponseCode())); } catch (Throwable t) { return new TranslatorException(t); } } protected BinaryWSProcedureExecution invokeHTTP(String method, String uri, String payload, Map<String, List<String>> headers) throws TranslatorException { if (LogManager.isMessageToBeRecorded(LogConstants.CTX_ODATA, MessageLevel.DETAIL)) { try { LogManager.logDetail(LogConstants.CTX_ODATA, "Source-URL=", URLDecoder.decode(uri, "UTF-8")); //$NON-NLS-1$ //$NON-NLS-2$ } catch (UnsupportedEncodingException e) { } } List<Argument> parameters = new ArrayList<Argument>(); parameters.add(new Argument(Direction.IN, new Literal(method, TypeFacility.RUNTIME_TYPES.STRING), null)); parameters.add(new Argument(Direction.IN, new Literal(payload, TypeFacility.RUNTIME_TYPES.STRING), null)); parameters.add(new Argument(Direction.IN, new Literal(uri, TypeFacility.RUNTIME_TYPES.STRING), null)); parameters.add(new Argument(Direction.IN, new Literal(true, TypeFacility.RUNTIME_TYPES.BOOLEAN), null)); //the engine currently always associates out params at resolve time even if the // values are not directly read by the call parameters.add(new Argument(Direction.OUT, TypeFacility.RUNTIME_TYPES.STRING, null)); Call call = this.translator.getLanguageFactory().createCall( ODataExecutionFactory.INVOKE_HTTP, parameters, null); BinaryWSProcedureExecution execution = new BinaryWSProcedureExecution( call, this.metadata, this.executionContext, null, this.connection); execution.setUseResponseContext(true); execution.setCustomHeaders(headers); execution.execute(); return execution; } protected Map<String, List<String>> getDefaultHeaders() { Map<String, List<String>> headers = new HashMap<String, List<String>>(); headers.put("Accept", Arrays.asList(ContentType.JSON.toContentTypeString())); //$NON-NLS-1$ headers.put("Content-Type", Arrays.asList( //$NON-NLS-1$ ContentType.APPLICATION_JSON.toContentTypeString())); if (this.executionContext != null) { headers.put("Prefer", Arrays.asList("odata.maxpagesize="+this.executionContext.getBatchSize())); //$NON-NLS-1$ //$NON-NLS-2$ } return headers; } protected InputStream executeSkipToken(URI nextURL, String baseURL, HttpStatusCode[] accepeted) throws TranslatorException { String next = nextURL.toString(); int idx = next.indexOf("$skiptoken="); //$NON-NLS-1$ if (next.toLowerCase().startsWith("http")) { //$NON-NLS-1$ return executeQuery("GET", nextURL.toString(), null, null, accepeted); //$NON-NLS-1$ } else if (idx != -1) { String skip = null; try { skip = next.substring(idx + 11); skip = URLDecoder.decode(skip, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new TranslatorException(e); } String nextUri = baseURL; if (baseURL.indexOf('?') == -1) { nextUri = baseURL + "?$skiptoken="+skip; //$NON-NLS-1$ } else { nextUri = baseURL + "&$skiptoken="+skip; //$NON-NLS-1$ } return executeQuery("GET", nextUri, null, null, accepeted); //$NON-NLS-1$ } else { throw new TranslatorException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17001, next)); } } @SuppressWarnings("unchecked") <T extends AbstractMetadataRecord> List<?> buildRow(T record, List<Column> columns, Class<?>[] expectedType, Map<String, Object> values) throws TranslatorException { List<Object> results = new ArrayList<Object>(); for (int i = 0; i < columns.size(); i++) { Column column = columns.get(i); T columnParent = (T)column.getParent(); String colName = column.getName(); if (!columnParent.equals(record)) { colName = getName(columnParent)+"/"+column.getName(); //$NON-NLS-1$ } Object value; try { value = ODataTypeManager.convertToTeiidRuntimeType(expectedType[i], values.get(colName), ODataMetadataProcessor.getNativeType(column)); } catch (TeiidException e) { throw new TranslatorException(e); } results.add(value); } return results; } public String getName(AbstractMetadataRecord table) { if (table.getNameInSource() != null) { return table.getNameInSource(); } return table.getName(); } }