/* * 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.odata; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.charset.Charset; import java.sql.Blob; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.ws.rs.core.Response.Status; import org.odata4j.core.ODataConstants; import org.odata4j.core.ODataConstants.Charsets; import org.odata4j.core.ODataVersion; import org.odata4j.core.OEntity; import org.odata4j.core.OError; import org.odata4j.core.OProperty; import org.odata4j.edm.EdmComplexType; import org.odata4j.edm.EdmDataServices; import org.odata4j.format.Entry; import org.odata4j.format.Feed; import org.odata4j.format.FormatParser; import org.odata4j.format.FormatParserFactory; import org.odata4j.format.FormatType; import org.odata4j.format.Settings; import org.odata4j.format.xml.AtomFeedFormatParser; import org.odata4j.stax2.XMLEvent2; import org.odata4j.stax2.XMLEventReader2; import org.odata4j.stax2.util.StaxUtil; 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.Column; import org.teiid.metadata.RuntimeMetadata; 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 Feed parse(Blob blob, ODataVersion version, String entityTable, EdmDataServices edsMetadata) throws TranslatorException { try { // if parser is written to return raw objects; then we can avoid some un-necessary object creation // due to time, I am not pursuing that now. FormatParser<Feed> parser = FormatParserFactory.getParser( Feed.class, FormatType.ATOM, new Settings(version, edsMetadata, entityTable, null)); return parser.parse(new InputStreamReader(blob.getBinaryStream())); } catch (SQLException e) { throw new TranslatorException(ODataPlugin.Event.TEIID17010, e, e.getMessage()); } } protected static ODataVersion getDataServiceVersion(String headerValue) { ODataVersion version = ODataConstants.DATA_SERVICE_VERSION; if (headerValue != null) { String[] str = headerValue.split(";"); //$NON-NLS-1$ version = ODataVersion.parse(str[0]); } return version; } protected ODataEntitiesResponse executeWithReturnEntity(String method, String uri, String payload, String entityTable, EdmDataServices edsMetadata, String eTag, Status... 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("application/atom+xml")); //$NON-NLS-1$ //$NON-NLS-2$ } BinaryWSProcedureExecution execution = executeDirect(method, uri, payload, headers); for (Status status:expectedStatus) { if (status.getStatusCode() == execution.getResponseCode()) { if (execution.getResponseCode() != Status.NO_CONTENT.getStatusCode() && execution.getResponseCode() != Status.NOT_FOUND.getStatusCode()) { Blob blob = (Blob)execution.getOutputParameterValues().get(0); ODataVersion version = getODataVersion(execution); Feed feed = parse(blob, version, entityTable, edsMetadata); return new ODataEntitiesResponse(uri, feed, entityTable, edsMetadata); } // this is success with no-data return new ODataEntitiesResponse(); } } // throw an error return new ODataEntitiesResponse(buildError(execution)); } ODataVersion getODataVersion(BinaryWSProcedureExecution execution) { return getDataServiceVersion(getHeader(execution, ODataConstants.Headers.DATA_SERVICE_VERSION)); } 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 ODataEntitiesResponse executeWithComplexReturn(String method, String uri, String payload, String complexTypeName, EdmDataServices edsMetadata, String eTag, Status... 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("application/atom+xml")); //$NON-NLS-1$ //$NON-NLS-2$ } BinaryWSProcedureExecution execution = executeDirect(method, uri, payload, headers); for (Status status:expectedStatus) { if (status.getStatusCode() == execution.getResponseCode()) { if (execution.getResponseCode() != Status.NO_CONTENT.getStatusCode()) { Blob blob = (Blob)execution.getOutputParameterValues().get(0); //ODataVersion version = getDataServiceVersion((String)execution.getResponseHeader(ODataConstants.Headers.DATA_SERVICE_VERSION)); EdmComplexType complexType = edsMetadata.findEdmComplexType(complexTypeName); if (complexType == null) { throw new RuntimeException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17016, complexType)); } try { return parserComplex(StaxUtil.newXMLEventReader(new InputStreamReader(blob.getBinaryStream())), complexType, edsMetadata); } catch (SQLException e) { throw new TranslatorException(ODataPlugin.Event.TEIID17010, e, e.getMessage()); } } // this is success with no-data return new ODataEntitiesResponse(); } } // throw an error return new ODataEntitiesResponse(buildError(execution)); } private ODataEntitiesResponse parserComplex(XMLEventReader2 reader, EdmComplexType complexType, EdmDataServices edsMetadata) { XMLEvent2 event = reader.nextEvent(); while (!event.isStartElement()) { event = reader.nextEvent(); } return new ODataEntitiesResponse(AtomFeedFormatParser.parseProperties(reader, event.asStartElement(), edsMetadata, complexType).iterator()); } protected TranslatorException buildError(BinaryWSProcedureExecution execution) { // do some error handling try { Blob blob = (Blob)execution.getOutputParameterValues().get(0); //FormatParser<OError> parser = FormatParserFactory.getParser(OError.class, FormatType.ATOM, null); FormatParser<OError> parser = new AtomErrorFormatParser(); OError error = parser.parse(new InputStreamReader(blob.getBinaryStream(), Charset.forName("UTF-8"))); //$NON-NLS-1$ return new TranslatorException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17013, execution.getResponseCode(), error.getCode(), error.getMessage(), error.getInnerError())); } catch (Throwable t) { return new TranslatorException(t); } } protected BinaryWSProcedureExecution executeDirect(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(FormatType.ATOM.getAcceptableMediaTypes())); //$NON-NLS-1$ headers.put("Content-Type", Arrays.asList("application/xml")); //$NON-NLS-1$ //$NON-NLS-2$ return headers; } class ODataEntitiesResponse { private Feed feed; private String uri; private Iterator<Entry> rowIter; private String entityTypeName; private TranslatorException exception; private Status[] acceptedStatus; private Iterator<OProperty<?>> complexValues; private EdmDataServices edsMetadata; public ODataEntitiesResponse(String uri, Feed feed, String entityTypeName, EdmDataServices edsMetadata, Status... accptedStatus) { this.uri = uri; this.feed = feed; this.entityTypeName = entityTypeName; this.rowIter = this.feed.getEntries().iterator(); this.acceptedStatus = accptedStatus; this.edsMetadata = edsMetadata; } public ODataEntitiesResponse(TranslatorException ex) { this.exception = ex; } public ODataEntitiesResponse() { } public ODataEntitiesResponse(Iterator<OProperty<?>> complexValues) { this.complexValues = complexValues; } public boolean hasRow() { return (this.rowIter != null && this.rowIter.hasNext()); } public boolean hasError() { return this.exception != null; } public TranslatorException getError() { return this.exception; } public List<?> getNextRow(Column[] columns, Class<?>[] expectedType) throws TranslatorException { if (this.rowIter != null && this.rowIter.hasNext()) { OEntity entity = this.rowIter.next().getEntity(); ArrayList results = new ArrayList(); for (int i = 0; i < columns.length; i++) { boolean isComplex = true; String colName = columns[i].getProperty(ODataMetadataProcessor.COLUMN_GROUP, false); if (colName == null) { colName = columns[i].getName(); isComplex = false; } Object value = entity.getProperty(colName).getValue(); if (isComplex) { List<OProperty<?>> embeddedProperties = (List<OProperty<?>>)value; for (OProperty prop:embeddedProperties) { if (prop.getName().equals(columns[i].getSourceName())) { value = prop.getValue(); break; } } } results.add(BaseQueryExecution.this.translator.retrieveValue(value, expectedType[i])); } fetchNextBatch(!this.rowIter.hasNext(), this.edsMetadata); return results; } else if (this.complexValues != null) { HashMap<String, Object> values = new HashMap<String, Object>(); while(this.complexValues.hasNext()) { OProperty prop = this.complexValues.next(); values.put(prop.getName(), prop.getValue()); } ArrayList results = new ArrayList(); for (int i = 0; i < columns.length; i++) { results.add(BaseQueryExecution.this.translator.retrieveValue(values.get(columns[i].getName()), expectedType[i])); } this.complexValues = null; return results; } return null; } // TODO:there is possibility here to async execute this feed private void fetchNextBatch(boolean fetch, EdmDataServices edsMetadata) throws TranslatorException { if (!fetch) { return; } String next = this.feed.getNext(); if (next == null) { this.feed = null; this.rowIter = null; return; } int idx = next.indexOf("$skiptoken="); //$NON-NLS-1$ if (idx != -1) { String skip = null; try { skip = next.substring(idx + 11); skip = URLDecoder.decode(skip, Charsets.Upper.UTF_8); } catch (UnsupportedEncodingException e) { throw new TranslatorException(e); } String nextUri = this.uri; if (this.uri.indexOf('?') == -1) { nextUri = this.uri + "?$skiptoken="+skip; //$NON-NLS-1$ } else { nextUri = this.uri + "&$skiptoken="+skip; //$NON-NLS-1$ } BinaryWSProcedureExecution execution = executeDirect("GET", nextUri, null, getDefaultHeaders()); //$NON-NLS-1$ validateResponse(execution); Blob blob = (Blob)execution.getOutputParameterValues().get(0); ODataVersion version = getODataVersion(execution); this.feed = parse(blob, version, this.entityTypeName, edsMetadata); this.rowIter = this.feed.getEntries().iterator(); } else if (next.toLowerCase().startsWith("http")) { //$NON-NLS-1$ BinaryWSProcedureExecution execution = executeDirect("GET", next, null, getDefaultHeaders()); //$NON-NLS-1$ validateResponse(execution); Blob blob = (Blob)execution.getOutputParameterValues().get(0); ODataVersion version = getDataServiceVersion((String)execution.getResponseHeader(ODataConstants.Headers.DATA_SERVICE_VERSION)); this.feed = parse(blob, version, this.entityTypeName, edsMetadata); this.rowIter = this.feed.getEntries().iterator(); } else { throw new TranslatorException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID17001, next)); } } private void validateResponse(BinaryWSProcedureExecution execution) throws TranslatorException { for (Status expected:this.acceptedStatus) { if (execution.getResponseCode() != expected.getStatusCode()) { throw buildError(execution); } } } Iterator<Entry> getResultsIter(){ return this.rowIter; } } }