/* * 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.jdbc; import static org.junit.Assert.*; import java.io.ByteArrayInputStream; import java.lang.Thread.UncaughtExceptionHandler; import java.nio.charset.Charset; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Properties; import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import javax.security.auth.Subject; import javax.security.auth.login.LoginException; import org.apache.cxf.common.security.SimplePrincipal; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.teiid.client.security.ILogon; import org.teiid.client.security.InvalidSessionException; import org.teiid.client.security.LogonException; import org.teiid.client.security.LogonResult; import org.teiid.client.util.ResultsFuture; import org.teiid.core.types.DataTypeManager; import org.teiid.core.util.Base64; import org.teiid.core.util.UnitTestUtil; import org.teiid.dqp.internal.datamgr.ConnectorManager; import org.teiid.dqp.internal.datamgr.ConnectorManagerRepository; import org.teiid.jdbc.FakeServer.DeployVDBParameter; import org.teiid.language.Command; import org.teiid.logging.LogConstants; import org.teiid.metadata.FunctionMethod; import org.teiid.metadata.FunctionMethod.PushDown; import org.teiid.metadata.FunctionParameter; import org.teiid.metadata.RuntimeMetadata; import org.teiid.net.socket.AuthenticationType; import org.teiid.query.function.metadata.FunctionCategoryConstants; import org.teiid.security.Credentials; import org.teiid.security.GSSResult; import org.teiid.security.SecurityHelper; import org.teiid.translator.DataNotAvailableException; import org.teiid.translator.Execution; import org.teiid.translator.ExecutionContext; import org.teiid.translator.ExecutionFactory; import org.teiid.translator.ResultSetExecution; import org.teiid.translator.TranslatorException; import org.teiid.transport.LogonImpl; @SuppressWarnings("nls") public class TestLocalConnections { private static final class MockSecurityHelper implements SecurityHelper { int calls; @Override public Subject getSubjectInContext(String securityDomain) { return currentContext; } @Override public Subject getSubjectInContext(Object context) { return (Subject)context; } @Override public Object getSecurityContext() { calls++; return currentContext; } @Override public void clearSecurityContext() { } @Override public Object associateSecurityContext(Object context) { Object result = currentContext; currentContext = (Subject)context; return result; } @Override public Object authenticate(String securityDomain, String baseUserName, Credentials credentials, String applicationName) throws LoginException { return null; } @Override public GSSResult negotiateGssLogin(String securityDomain, byte[] serviceTicket) throws LoginException { return null; } } private final class SimpleUncaughtExceptionHandler implements UncaughtExceptionHandler { volatile Throwable t; @Override public void uncaughtException(Thread arg0, Throwable arg1) { t = arg1; } } static ReentrantLock lock = new ReentrantLock(); static Condition waiting = lock.newCondition(); static Condition wait = lock.newCondition(); static Semaphore sourceCounter = new Semaphore(0); public static int blocking() throws InterruptedException { lock.lock(); try { waiting.signal(); if (!wait.await(2, TimeUnit.SECONDS)) { throw new RuntimeException(); } } finally { lock.unlock(); } return 1; } static FakeServer server = new FakeServer(true); @SuppressWarnings("serial") @BeforeClass public static void oneTimeSetup() throws Exception { server.setUseCallingThread(true); server.setConnectorManagerRepository(new ConnectorManagerRepository() { @Override public ConnectorManager getConnectorManager(String connectorName) { return new ConnectorManager(connectorName, connectorName) { @Override public ExecutionFactory<Object, Object> getExecutionFactory() { return new ExecutionFactory<Object, Object>() { @Override public boolean isSourceRequired() { return false; } @Override public Execution createExecution(Command command, ExecutionContext executionContext, RuntimeMetadata metadata, Object connection) throws TranslatorException { return new ResultSetExecution() { boolean returnedRow = false; @Override public void execute() throws TranslatorException { lock.lock(); try { sourceCounter.release(); if (!wait.await(5, TimeUnit.SECONDS)) { throw new RuntimeException(); } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } } @Override public void close() { } @Override public void cancel() throws TranslatorException { } @Override public List<?> next() throws TranslatorException, DataNotAvailableException { if (returnedRow) { return null; } returnedRow = true; return new ArrayList<Object>(Collections.singleton(null)); } }; } }; } @Override public Object getConnectionFactory() throws TranslatorException { return null; } }; } }); FunctionMethod function = new FunctionMethod("foo", null, FunctionCategoryConstants.MISCELLANEOUS, PushDown.CANNOT_PUSHDOWN, TestLocalConnections.class.getName(), "blocking", null, new FunctionParameter("result", DataTypeManager.DefaultDataTypes.INTEGER), false, FunctionMethod.Determinism.NONDETERMINISTIC); HashMap<String, Collection<FunctionMethod>> udfs = new HashMap<String, Collection<FunctionMethod>>(); udfs.put("test", Arrays.asList(function)); server.deployVDB("PartsSupplier", UnitTestUtil.getTestDataPath() + "/PartsSupplier.vdb", new DeployVDBParameter(udfs, null)); } @AfterClass public static void oneTimeTearDown() { server.stop(); } @Test public void testConcurrentExection() throws Throwable { Thread t = new Thread() { @Override public void run() { try { Connection c = server.createConnection("jdbc:teiid:PartsSupplier"); Statement s = c.createStatement(); s.execute("select foo()"); s.close(); } catch (Exception e) { throw new RuntimeException(e); } } }; SimpleUncaughtExceptionHandler handler = new SimpleUncaughtExceptionHandler(); t.setUncaughtExceptionHandler(handler); t.start(); lock.lock(); try { waiting.await(); } finally { lock.unlock(); } Connection c = server.createConnection("jdbc:teiid:PartsSupplier"); Statement s = c.createStatement(); s.execute("select * from tables"); lock.lock(); try { wait.signal(); } finally { lock.unlock(); } t.join(2000); if (t.isAlive()) { fail(); } s.close(); if (handler.t != null) { throw handler.t; } } @Test public void testUseInDifferentThreads() throws Throwable { for (int i = 0; !server.getDqp().getRequests().isEmpty() && i < 40; i++) { //the previous test may not have cleaned up Thread.sleep(50); } int count = server.getDqp().getRequests().size(); Connection c = server.createConnection("jdbc:teiid:PartsSupplier"); final Statement s = c.createStatement(); s.execute("select 1"); assertFalse(server.getDqp().getRequests().isEmpty()); Thread t = new Thread() { @Override public void run() { try { s.close(); } catch (SQLException e) { throw new RuntimeException(e); } } }; SimpleUncaughtExceptionHandler handler = new SimpleUncaughtExceptionHandler(); t.setUncaughtExceptionHandler(handler); t.start(); t.join(2000); if (t.isAlive()) { fail(); } if (handler.t != null) { throw handler.t; } for (int i = 0; server.getDqp().getRequests().size() != count && i < 40; i++) { //the concurrent modification may not be seen initially Thread.sleep(50); } assertEquals(count, server.getDqp().getRequests().size()); } @Test public void testWait() throws Throwable { final Connection c = server.createConnection("jdbc:teiid:PartsSupplier"); Thread t = new Thread() { @Override public void run() { Statement s; try { s = c.createStatement(); assertTrue(s.execute("select part_id from parts")); } catch (SQLException e) { throw new RuntimeException(e); } } }; t.start(); SimpleUncaughtExceptionHandler handler = new SimpleUncaughtExceptionHandler(); t.setUncaughtExceptionHandler(handler); sourceCounter.acquire(); //t should now be waiting also lock.lock(); try { wait.signal(); } finally { lock.unlock(); } //t should finish t.join(); if (handler.t != null) { throw handler.t; } } @Test public void testWaitMultiple() throws Throwable { final Connection c = server.createConnection("jdbc:teiid:PartsSupplier"); Thread t = new Thread() { @Override public void run() { Statement s; try { s = c.createStatement(); assertTrue(s.execute("select part_id from parts union all select part_name from parts")); ResultSet r = s.getResultSet(); //wake up the other source thread, should put the requestworkitem into the more work state lock.lock(); try { wait.signal(); } finally { lock.unlock(); } Thread.sleep(1000); //TODO: need a better hook to determine that connector work has finished while (r.next()) { //will hang unless this thread is allowed to resume processing } } catch (Exception e) { throw new RuntimeException(e); } } }; t.start(); SimpleUncaughtExceptionHandler handler = new SimpleUncaughtExceptionHandler(); t.setUncaughtExceptionHandler(handler); sourceCounter.acquire(2); //t should now be waiting also //wake up 1 source thread lock.lock(); try { wait.signal(); } finally { lock.unlock(); } t.join(); if (handler.t != null) { throw handler.t; } } @Test public void testWaitForLoad() throws Exception { final ResultsFuture<Void> future = new ResultsFuture<Void>(); Thread t = new Thread() { @Override public void run() { try { server.createConnection("jdbc:teiid:not_there.1"); future.getResultsReceiver().receiveResults(null); } catch (Exception e) { future.getResultsReceiver().exceptionOccurred(e); } } }; t.setDaemon(true); t.start(); assertFalse(future.isDone()); try { server.deployVDB("not_there", UnitTestUtil.getTestDataPath() + "/PartsSupplier.vdb"); future.get(5000, TimeUnit.SECONDS); } finally { server.undeployVDB("not_there"); } try { server.createConnection("jdbc:teiid:not_there.1;waitForLoad=0"); fail(); } catch (TeiidSQLException e) { } } @Test public void testWaitForLoadTimeout() throws Exception { final ResultsFuture<Void> future = new ResultsFuture<Void>(); Thread t = new Thread() { @Override public void run() { try { server.createConnection("jdbc:teiid:not_there.1;waitForLoad=1000"); future.getResultsReceiver().receiveResults(null); } catch (Exception e) { future.getResultsReceiver().exceptionOccurred(e); } } }; t.setDaemon(true); t.start(); assertFalse(future.isDone()); try { server.deployVDB(new ByteArrayInputStream( "<vdb name=\"not_there\" version=\"1\"><model name=\"myschema\"><source name=\"x\" translator-name=\"x\" connection-jndi-name=\"x\"/></model></vdb>".getBytes(Charset.forName("UTF-8")))); fail(); } catch (TranslatorException e) { //no connection factory } try { future.get(5000, TimeUnit.SECONDS); fail(); } catch (ExecutionException e) { assertTrue(e.getMessage().contains("TEIID40097")); } finally { server.undeployVDB("not_there"); } } static Subject currentContext = new Subject(); @Test public void testPassThroughDifferentUsers() throws Throwable { MockSecurityHelper securityHelper = new MockSecurityHelper(); SecurityHelper current = server.getSessionService().getSecurityHelper(); server.getClientServiceRegistry().setSecurityHelper(securityHelper); server.getSessionService().setSecurityHelper(securityHelper); try { final Connection c = server.createConnection("jdbc:teiid:PartsSupplier;PassthroughAuthentication=true"); Statement s = c.createStatement(); ResultSet rs = s.executeQuery("select session_id()"); Subject o = currentContext; currentContext = null; s.cancel(); currentContext = o; rs.next(); String id = rs.getString(1); rs.close(); assertEquals(4, securityHelper.calls); server.getSessionService().pingServer(id); currentContext = new Subject(); currentContext.getPrincipals().add(new SimplePrincipal("x")); rs = s.executeQuery("select session_id()"); rs.next(); String id1 = rs.getString(1); rs.close(); assertFalse(id.equals(id1)); try { server.getSessionService().pingServer(id); //should have logged off fail(); } catch (InvalidSessionException e) { } } finally { server.getClientServiceRegistry().setSecurityHelper(current); server.getSessionService().setSecurityHelper(current); } } @Test public void testSimulateGSSWithODBC() throws Throwable { SecurityHelper securityHelper = new MockSecurityHelper(); SecurityHelper current = server.getSessionService().getSecurityHelper(); server.getClientServiceRegistry().setSecurityHelper(securityHelper); server.getSessionService().setSecurityHelper(securityHelper); server.getSessionService().setAuthenticationType(AuthenticationType.GSS); final byte[] token = "This is test of Partial GSS API".getBytes(); final AtomicBoolean set = new AtomicBoolean(true); LogonImpl login = new LogonImpl(server.getSessionService(), null) { @Override public LogonResult logon(Properties connProps) throws LogonException { if (set.get()) { this.gssServiceTickets.put(Base64.encodeBytes(MD5(token)), currentContext); set.set(false); } return super.logon(connProps); } }; server.getClientServiceRegistry().registerClientService(ILogon.class, login, LogConstants.CTX_SECURITY); try { Properties prop = new Properties(); prop.put(ILogon.KRB5TOKEN, token); final Connection c = server.createConnection("jdbc:teiid:PartsSupplier;user=GSS", prop); Statement s = c.createStatement(); ResultSet rs = s.executeQuery("select session_id()"); Subject o = currentContext; currentContext = null; s.cancel(); currentContext = o; rs.next(); String id = rs.getString(1); rs.close(); } finally { server.getSessionService().setAuthenticationType(AuthenticationType.USERPASSWORD); server.getClientServiceRegistry().setSecurityHelper(current); server.getSessionService().setSecurityHelper(current); } } @Test public void testFetchSize() throws Exception { Connection c = server.createConnection("jdbc:teiid:PartsSupplier;FetchSize=10;"); Statement s = c.createStatement(); //a query that spans multiple batches and has a larger batch size than the fetch size ResultSet rs = s.executeQuery("select * from tables t, tables t1"); while (rs.next()) { } } }