/* * 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.olingo.service; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import org.teiid.adminapi.VDB.Status; import org.teiid.adminapi.impl.VDBMetaData; import org.teiid.core.TeiidProcessingException; import org.teiid.core.TeiidRuntimeException; import org.teiid.core.util.PropertiesUtils; import org.teiid.jdbc.ConnectionImpl; import org.teiid.jdbc.LocalProfile; import org.teiid.jdbc.PreparedStatementImpl; import org.teiid.jdbc.TeiidDriver; import org.teiid.logging.LogConstants; import org.teiid.logging.LogManager; import org.teiid.metadata.MetadataStore; import org.teiid.net.TeiidURL; import org.teiid.odata.api.Client; import org.teiid.odata.api.CountResponse; import org.teiid.odata.api.OperationResponse; import org.teiid.odata.api.ProcedureReturnType; import org.teiid.odata.api.QueryResponse; import org.teiid.odata.api.SQLParameter; import org.teiid.odata.api.UpdateResponse; import org.teiid.odbc.ODBCServerRemoteImpl; import org.teiid.olingo.ODataPlugin; import org.teiid.query.QueryPlugin; import org.teiid.query.metadata.TransformationMetadata; import org.teiid.query.sql.lang.CacheHint; import org.teiid.query.sql.lang.Command; import org.teiid.query.sql.lang.Limit; import org.teiid.query.sql.lang.Query; import org.teiid.query.sql.symbol.Constant; import org.teiid.query.sql.symbol.Reference; import org.teiid.query.sql.visitor.ReferenceCollectorVisitor; import org.teiid.translator.CacheDirective; import org.teiid.transport.LocalServerConnection; public class LocalClient implements Client { static final String DELIMITER = "," ; //$NON-NLS-1$ private volatile VDBMetaData vdb; private final String vdbName; private final String vdbVersion; private ConnectionImpl connection; private Properties properties; public LocalClient(String vdbName, String vdbVersion, Properties properties) { this.vdbName = vdbName; this.vdbVersion = vdbVersion; this.properties = properties; } private long getCacheTime() { return PropertiesUtils.getLongProperty(this.properties, Client.SKIPTOKEN_TIME, 300000L); } @Override public Connection open() throws SQLException, TeiidProcessingException { this.connection = buildConnection(TeiidDriver.getInstance(), this.vdbName, this.vdbVersion, this.properties); ODBCServerRemoteImpl.setConnectionProperties(connection); ODBCServerRemoteImpl.setConnectionProperties(connection, this.properties); getVDBInternal(); return this.connection; } @Override public void close() throws SQLException { if (this.connection != null) { this.connection.close(); } } public ConnectionImpl getConnection() { return this.connection; } public static ConnectionImpl buildConnection(TeiidDriver driver, String vdbName, String version, Properties props) throws SQLException { StringBuilder sb = new StringBuilder(); sb.append("jdbc:teiid:").append(vdbName); //$NON-NLS-1$ if (version != null) { sb.append(".").append(version); //$NON-NLS-1$ } sb.append(";"); //$NON-NLS-1$ if (props.getProperty(TeiidURL.CONNECTION.PASSTHROUGH_AUTHENTICATION) == null) { props.setProperty(TeiidURL.CONNECTION.PASSTHROUGH_AUTHENTICATION, "true"); //$NON-NLS-1$ } if (props.getProperty(LocalProfile.TRANSPORT_NAME) == null) { props.setProperty(LocalProfile.TRANSPORT_NAME, "odata"); } if (props.getProperty(LocalProfile.WAIT_FOR_LOAD) == null) { props.setProperty(LocalProfile.WAIT_FOR_LOAD, "0"); //$NON-NLS-1$ } ConnectionImpl connection = driver.connect(sb.toString(), props); return connection; } @Override public VDBMetaData getVDB() { try { return getVDBInternal(); } catch (TeiidProcessingException e) { throw new TeiidRuntimeException(e); } catch (SQLException e) { throw new TeiidRuntimeException(e); } } private VDBMetaData getVDBInternal() throws SQLException, TeiidProcessingException { if (this.vdb == null) { LocalServerConnection lsc = (LocalServerConnection) getConnection().getServerConnection(); vdb = lsc.getWorkContext().getVDB(); if (vdb == null) { throw new TeiidRuntimeException(ODataPlugin.Util.gs( ODataPlugin.Event.TEIID16001, this.vdbName, this.vdbVersion)); } this.vdb = vdb; } if (vdb.getStatus() != Status.ACTIVE) { throw new TeiidProcessingException(QueryPlugin.Event.TEIID31099, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31099, vdb, vdb.getStatus())); } return this.vdb; } @Override public void executeCall(String sql, List<SQLParameter> parameters, ProcedureReturnType returnType, OperationResponse response) throws SQLException { LogManager.logDetail(LogConstants.CTX_ODATA, "Teiid-Query:",sql); //$NON-NLS-1$ final CallableStatement stmt = getConnection().prepareCall(sql); int i = 1; if (!returnType.hasResultSet()) { stmt.registerOutParameter(i++, returnType.getSqlType()); } if (!parameters.isEmpty()) { for (SQLParameter param:parameters) { stmt.setObject(i++, param.getValue(), param.getSqlType()); } } boolean results = stmt.execute(); if (results) { final ResultSet rs = stmt.getResultSet(); while (rs.next()) { response.addRow(rs); } } if (!returnType.hasResultSet()) { Object result = stmt.getObject(1); response.setReturnValue(result); } } @Override public MetadataStore getMetadataStore() { return getVDB().getAttachment(TransformationMetadata.class).getMetadataStore(); } @Override public void executeSQL(Query query, List<SQLParameter> parameters, boolean calculateTotalSize, Integer skipOption, Integer topOption, String nextOption, int pageSize, final QueryResponse response) throws SQLException { boolean cache = pageSize > 0; if (cache) { CacheHint hint = new CacheHint(); hint.setTtl(getCacheTime()); hint.setScope(CacheDirective.Scope.USER); query.setCacheHint(hint); } boolean getCount = false; getCount = calculateTotalSize; boolean skipAndTopApplied = false; if (!getCount && (topOption != null || skipOption != null)) { query.setLimit(new Limit(skipOption!=null?new Constant(skipOption):null, topOption!=null?new Constant(topOption):null)); skipAndTopApplied=true; } String sessionId = getConnection().getServerConnection().getLogonResult().getSessionID(); String nextToken = null; Integer savedEntityCount = null; if (nextOption != null) { nextToken = nextOption; if (cache) { StringTokenizer st = new StringTokenizer(nextOption, DELIMITER); sessionId = st.nextToken(); nextToken = st.nextToken(); if (st.hasMoreTokens()) { savedEntityCount = Integer.parseInt(st.nextToken()); } } getCount = false; // the URL might have $count=true, but ignore it. } String sql = query.toString(); if (cache) { sql += " /* "+ sessionId +" */"; //$NON-NLS-1$ //$NON-NLS-2$ } LogManager.logDetail(LogConstants.CTX_ODATA, "Teiid-Query:",sql); //$NON-NLS-1$ final PreparedStatement stmt = getConnection().prepareStatement(sql, cache?ResultSet.TYPE_SCROLL_INSENSITIVE:ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); if (parameters!= null && !parameters.isEmpty()) { List<Reference> references = ReferenceCollectorVisitor.getReferences(query); for (int i = 0; i < references.size(); i++) { int index = references.get(i).getIndex(); stmt.setObject(i+1, parameters.get(index).getValue(), parameters.get(index).getSqlType()); } } final ResultSet rs = stmt.executeQuery(); //skip to the initial position int count = 0; int entityCount = 0; int skipSize = 0; //skip based upon the skip value if (nextToken == null && skipOption != null && !skipAndTopApplied) { if (skipOption > 0) { int s = skipEntities(rs, skipOption); count += s; entityCount = s; skipSize = count; } } //skip based upon the skipToken if (nextToken != null) { skipSize += Integer.parseInt(nextToken); if (skipSize > 0) { count += skip(cache, rs, skipSize); } } //determine the number of records to return int size = pageSize; int top = Integer.MAX_VALUE; if (getCount && topOption != null) { top = topOption; size = top; if (pageSize > 0) { size = Math.min(pageSize, size); } } else if (size < 1) { size = Integer.MAX_VALUE; } //build the results int i = 0; int nextCount = count; while(true) { if (!rs.next()) { break; } count++; i++; entityCount++; if (i > size) { break; } nextCount++; response.addRow(rs); } //set the count if (getCount) { while (rs.next()) { count++; entityCount++; } } if (savedEntityCount != null) { response.setCount(savedEntityCount); } else { response.setCount(entityCount); } //set the skipToken if needed if (cache && response.size() == pageSize) { long end = nextCount; if (getCount) { if (end < Math.min(top, count)) { response.setNextToken(nextToken(cache, sessionId, end, entityCount)); } } else if (count != nextCount){ response.setNextToken(nextToken(cache, sessionId, end, null)); //will force the entry to cache or is effectively a no-op when already cached rs.last(); } } } private String nextToken(boolean cache, String sessionid, long skip, Integer entityCount) { if (cache) { String token = sessionid+DELIMITER+String.valueOf(skip); if (entityCount != null) { token = token+DELIMITER+String.valueOf(entityCount); } return token; } return String.valueOf(skip); } private int skip(boolean cache, final ResultSet rs, int skipSize) throws SQLException { int skipped = 0; if (!cache) { for (int i = 0; i < skipSize; i++) { skipped++; if (!rs.next()) { break; } } } else { skipped = skipSize; rs.absolute(skipSize); } return skipped; } private int skipEntities(final ResultSet rs, int skipEntities) throws SQLException { int skipped = 0; while (rs.next()) { skipped++; if (skipped == skipEntities) { break; } } return skipped; } @Override public CountResponse executeCount(Query query, List<SQLParameter> parameters) throws SQLException { String sql = query.toString(); LogManager.logDetail(LogConstants.CTX_ODATA, "Teiid-Query:", sql); //$NON-NLS-1$ final PreparedStatement stmt = getConnection().prepareStatement(sql); if (!parameters.isEmpty()) { for (int i = 0; i < parameters.size(); i++) { stmt.setObject(i + 1, parameters.get(i).getValue(), parameters.get(i).getSqlType()); } } ResultSet rs = stmt.executeQuery(); rs.next(); final int count = rs.getInt(1); rs.close(); stmt.close(); return new CountResponse() { @Override public int getCount() { return count; } }; } @Override public UpdateResponse executeUpdate(Command query, List<SQLParameter> parameters) throws SQLException { String sql = query.toString(); LogManager.logDetail(LogConstants.CTX_ODATA, "Teiid-Query:", sql); //$NON-NLS-1$ final PreparedStatementImpl stmt = getConnection().prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT, Statement.RETURN_GENERATED_KEYS); if (!parameters.isEmpty()) { for (int i = 0; i < parameters.size(); i++) { stmt.setObject(i + 1, parameters.get(i).getValue(), parameters.get(i).getSqlType()); } } final int count = stmt.executeUpdate(); final Map<String, Object> keys = getGeneratedKeys(stmt.getGeneratedKeys()); stmt.close(); return new UpdateResponse() { @Override public Map<String, Object> getGeneratedKeys() { return keys; } @Override public int getUpdateCount() { return count; } }; } private Map<String, Object> getGeneratedKeys(ResultSet result) throws SQLException { if (result == null) { return null; } HashMap<String, Object> keys = new HashMap<String, Object>(); ResultSetMetaData metadata = result.getMetaData(); // now read the values while (result.next()) { for (int i = 0; i < metadata.getColumnCount(); i++) { String label = metadata.getColumnLabel(i + 1); keys.put(label, result.getObject(i + 1)); } } return keys; } @Override public String getProperty(String key) { return this.properties.getProperty(key); } @Override public String startTransaction() throws SQLException { getConnection().setAutoCommit(false); return "anyid"; } @Override public void commit(String txnId) throws SQLException { getConnection().commit(); getConnection().setAutoCommit(true); } @Override public void rollback(String txnId) throws SQLException { getConnection().rollback(); getConnection().setAutoCommit(true); } }