/*
* 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();
}
}