/*
* 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.transport;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import org.teiid.client.DQP;
import org.teiid.client.security.ILogon;
import org.teiid.client.security.LogonException;
import org.teiid.client.security.LogonResult;
import org.teiid.client.util.ExceptionUtil;
import org.teiid.client.util.ResultsFuture;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.core.util.PropertiesUtils;
import org.teiid.deployers.VDBLifeCycleListener;
import org.teiid.deployers.VDBRepository;
import org.teiid.dqp.internal.process.DQPWorkContext;
import org.teiid.gss.MakeGSS;
import org.teiid.jdbc.JDBCPlugin;
import org.teiid.jdbc.LocalProfile;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.net.CommunicationException;
import org.teiid.net.ConnectionException;
import org.teiid.net.ServerConnection;
import org.teiid.net.TeiidURL;
import org.teiid.net.socket.AuthenticationType;
import org.teiid.net.socket.Handshake;
import org.teiid.runtime.RuntimePlugin;
import org.teiid.vdb.runtime.VDBKey;
public class LocalServerConnection implements ServerConnection {
private static final String TEIID_RUNTIME_CONTEXT = "teiid/queryengine"; //$NON-NLS-1$
private LogonResult result;
private boolean shutdown;
private ClientServiceRegistry csr;
private DQPWorkContext workContext = new DQPWorkContext();
private Properties connectionProperties;
private boolean passthrough;
private boolean derived;
private static String serverVersion = new Handshake().getVersion();
private Method cancelMethod;
public static String jndiNameForRuntime(String embeddedTransportName) {
return TEIID_RUNTIME_CONTEXT+"/"+embeddedTransportName; //$NON-NLS-1$
}
public LocalServerConnection(Properties connectionProperties, boolean useCallingThread) throws CommunicationException, ConnectionException{
this.connectionProperties = connectionProperties;
this.csr = getClientServiceRegistry(connectionProperties.getProperty(LocalProfile.TRANSPORT_NAME, "local")); //$NON-NLS-1$
DQPWorkContext context = (DQPWorkContext)connectionProperties.get(LocalProfile.DQP_WORK_CONTEXT);
if (context == null) {
String vdbVersion = connectionProperties.getProperty(TeiidURL.JDBC.VDB_VERSION);
String vdbName = connectionProperties.getProperty(TeiidURL.JDBC.VDB_NAME);
VDBKey key = new VDBKey(vdbName, vdbVersion);
if (!key.isAtMost()) {
int waitForLoad = PropertiesUtils.getIntProperty(connectionProperties, TeiidURL.CONNECTION.LOGIN_TIMEOUT, -1);
if (waitForLoad == -1) {
waitForLoad = PropertiesUtils.getIntProperty(connectionProperties, LocalProfile.WAIT_FOR_LOAD, -1);
} else {
waitForLoad *= 1000; //seconds to milliseconds
}
if (waitForLoad != 0) {
this.csr.waitForFinished(key, waitForLoad);
}
}
workContext.setSecurityHelper(csr.getSecurityHelper());
workContext.setUseCallingThread(useCallingThread);
workContext.setSecurityContext(csr.getSecurityHelper().getSecurityContext());
authenticate();
passthrough = Boolean.valueOf(connectionProperties.getProperty(TeiidURL.CONNECTION.PASSTHROUGH_AUTHENTICATION, "false")); //$NON-NLS-1$
} else {
derived = true;
workContext = context;
this.result = new LogonResult(context.getSessionToken(), context.getVdbName(), null);
passthrough = true;
}
try {
cancelMethod = DQP.class.getMethod("cancelRequest", new Class[] {long.class}); //$NON-NLS-1$
} catch (SecurityException e) {
throw new TeiidRuntimeException(e);
} catch (NoSuchMethodException e) {
throw new TeiidRuntimeException(e);
}
}
protected ClientServiceRegistry getClientServiceRegistry(String transport) {
try {
InitialContext ic = new InitialContext();
return (ClientServiceRegistry)ic.lookup(jndiNameForRuntime(transport));
} catch (NamingException e) {
throw new TeiidRuntimeException(RuntimePlugin.Event.TEIID40067, e);
}
}
public synchronized void authenticate() throws ConnectionException, CommunicationException {
Object previousSecurityContext = workContext.getSecurityHelper().associateSecurityContext(workContext.getSession().getSecurityContext());
try {
logoff();
} finally {
workContext.getSecurityHelper().associateSecurityContext(previousSecurityContext);
}
workContext.setSecurityContext(previousSecurityContext);
try {
this.result = this.getService(ILogon.class).logon(this.connectionProperties);
AuthenticationType type = (AuthenticationType) this.result.getProperty(ILogon.AUTH_TYPE);
if (type != null) {
//server has issued an additional challenge
if (type == AuthenticationType.GSS) {
try {
this.result = MakeGSS.authenticate(this.getService(ILogon.class), this.connectionProperties);
} catch (LogonException e) {
if (!passthrough) {
throw new LogonException(RuntimePlugin.Event.TEIID40150, e, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40150));
}
throw e;
}
} else {
throw new LogonException(JDBCPlugin.Event.TEIID20034, JDBCPlugin.Util.gs(JDBCPlugin.Event.TEIID20034, type));
}
}
} catch (LogonException e) {
//TODO: above we make a special check for gss if not passthrough, we could do the same in general here or in sessionserviceimpl
// Propagate the original message as it contains the message we want
// to give to the user
throw new ConnectionException(e);
} catch (TeiidComponentException e) {
if (e.getCause() instanceof CommunicationException) {
throw (CommunicationException)e.getCause();
}
throw new CommunicationException(RuntimePlugin.Event.TEIID40069, e);
}
}
public <T> T getService(final Class<T> iface) {
return iface.cast(Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {iface}, new InvocationHandler() {
boolean logon = iface.equals(ILogon.class);
public Object invoke(Object arg0, final Method arg1, final Object[] arg2) throws Throwable {
if (shutdown) {
throw ExceptionUtil.convertException(arg1, new TeiidComponentException(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40074)));
}
try {
// check to make sure the current security context same as logged one
if (passthrough && !logon
&& !arg1.equals(cancelMethod) // -- it's ok to use another thread to cancel
&& !workContext.getSession().isClosed()
//if configured without a security domain the context will be null
&& workContext.getSession().getSecurityDomain() != null
&& !sameSubject(workContext)) {
//TODO: this is an implicit changeUser - we may want to make this explicit, but that would require pools to explicitly use changeUser
LogManager.logInfo(LogConstants.CTX_SECURITY, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40115, workContext.getSession().getSessionId()));
authenticate();
}
final T service = csr.getClientService(iface);
return workContext.runInContext(new Callable<Object>() {
public Object call() throws Exception {
return arg1.invoke(service, arg2);
}
});
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Throwable e) {
throw ExceptionUtil.convertException(arg1, e);
}
}
}));
}
public static boolean sameSubject(DQPWorkContext workContext) {
Object currentContext = workContext.getSecurityHelper().getSecurityContext();
if (currentContext != null) {
Subject currentUser = workContext.getSecurityHelper().getSubjectInContext(workContext.getSecurityDomain());
if (workContext.getSubject() != null && currentUser != null && workContext.getSubject().equals(currentUser)) {
return true;
}
if (currentUser == null && workContext.getSubject() == null) {
return true; //unauthenticated
}
}
return false;
}
@Override
public boolean isOpen(long msToTest) {
if (shutdown) {
return false;
}
try {
ResultsFuture<?> result = this.getService(ILogon.class).ping();
result.get(msToTest, TimeUnit.MILLISECONDS);
} catch (Exception e) {
return false;
}
return true;
}
public void close() {
shutdown(true);
}
private void shutdown(boolean logoff) {
if (shutdown) {
return;
}
if (logoff) {
logoff();
}
this.shutdown = true;
}
private void logoff() {
if (derived) {
return; //not the right place to kill the session
}
try {
//make a best effort to send the logoff
Future<?> writeFuture = this.getService(ILogon.class).logoff();
if (writeFuture != null) {
writeFuture.get(5000, TimeUnit.MILLISECONDS);
}
} catch (Exception e) {
//ignore
}
}
public LogonResult getLogonResult() {
return result;
}
@Override
public boolean isSameInstance(ServerConnection conn) throws CommunicationException {
return (conn instanceof LocalServerConnection);
}
@Override
public void cleanUp() {
}
@Override
public boolean supportsContinuous() {
return true;
}
public DQPWorkContext getWorkContext() {
return workContext;
}
@Override
public boolean isLocal() {
return true;
}
public void addListener(VDBLifeCycleListener listener) {
VDBRepository repo = csr.getVDBRepository();
if (repo != null) {
repo.addListener(listener);
}
}
public void removeListener(VDBLifeCycleListener listener) {
VDBRepository repo = csr.getVDBRepository();
if (repo != null) {
repo.removeListener(listener);
}
}
@Override
public String getServerVersion() {
return serverVersion;
}
}