/* This file is part of the db4o object database http://www.db4o.com Copyright (C) 2004 - 2011 Versant Corporation http://www.versant.com db4o is free software; you can redistribute it and/or modify it under the terms of version 3 of the GNU General Public License as published by the Free Software Foundation. db4o 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. */ package com.db4o.cs.internal; import java.io.*; import com.db4o.*; import com.db4o.cs.foundation.*; import com.db4o.cs.internal.messages.*; import com.db4o.events.Event4; import com.db4o.ext.*; import com.db4o.foundation.*; import com.db4o.internal.*; import com.db4o.internal.events.Event4Impl; public final class ServerMessageDispatcherImpl implements ServerMessageDispatcher, Runnable { private String _clientName; private boolean _loggedin; private boolean _closeMessageSent; private final ObjectServerImpl _server; private Socket4Adapter _socket; private final ClientTransactionHandle _transactionHandle; private Hashtable4 _queryResults; final int _threadID; private CallbackObjectInfoCollections _committedInfo; private boolean _caresAboutCommitted; private boolean _isClosed; private final Object _lock = new Object(); private final Object _mainLock; private final Event4Impl<MessageEventArgs> _messageReceived = Event4Impl.newInstance(); private Thread _thread; ServerMessageDispatcherImpl(ObjectServerImpl server, ClientTransactionHandle transactionHandle, Socket4 socket4, int threadID, boolean loggedIn, Object mainLock) throws Exception { _mainLock = mainLock; _transactionHandle = transactionHandle; _loggedin = loggedIn; _server = server; _threadID = threadID; _socket = new Socket4Adapter(socket4); _socket.setSoTimeout(((Config4Impl) server.configure()) .timeoutServerSocket()); // TODO: Experiment with packetsize and noDelay // i_socket.setSendBufferSize(100); // i_socket.setTcpNoDelay(true); } public boolean close() { return close(ShutdownMode.NORMAL); } public boolean close(ShutdownMode mode) { synchronized(_lock) { if (!isMessageDispatcherAlive()) { return true; } _isClosed = true; } synchronized(_mainLock) { _transactionHandle.releaseTransaction(mode); if(! mode.isFatal()){ sendCloseMessage(); } _transactionHandle.close(mode); closeSocket(); removeFromServer(); return true; } } public void closeConnection() { synchronized (_lock) { if (!isMessageDispatcherAlive()) { return; } _isClosed = true; } synchronized (_mainLock) { closeSocket(); removeFromServer(); } } public boolean isMessageDispatcherAlive() { synchronized(_lock){ return !_isClosed; } } private void sendCloseMessage() { try { if (! _closeMessageSent) { _closeMessageSent = true; write(Msg.CLOSE); } } catch (Exception e) { if (Debug4.atHome) { e.printStackTrace(); } } } private void removeFromServer() { try { _server.removeThread(this); } catch (Exception e) { if (Debug4.atHome) { e.printStackTrace(); } } } private void closeSocket() { try { if(_socket != null) { _socket.close(); } } catch (Db4oIOException e) { if (Debug4.atHome) { e.printStackTrace(); } } } public Transaction transaction() { return _transactionHandle.transaction(); } public void run() { _thread = Thread.currentThread(); try{ setDispatcherName("" + _threadID); _server.withEnvironment(new Runnable(){ public void run() { messageLoop(); }}); }finally{ close(); } } private void messageLoop(){ while (isMessageDispatcherAlive()) { try { if(! messageProcessor()){ return; } } catch (Db4oIOException e) { if(DTrace.enabled){ DTrace.ADD_TO_CLASS_INDEX.log(e.toString()); } return; } } } private boolean messageProcessor() throws Db4oIOException{ Msg message = Msg.readMessage(this, transaction(), _socket); if(message == null){ return true; } triggerMessageReceived(message); if(!_loggedin && !Msg.LOGIN.equals(message)) { return true; } // TODO: COR-885 - message may process against closed server // Checking aliveness just makes the issue less likely to occur. Naive synchronization against main lock is prohibitive. return processMessage(message); } public boolean processMessage(Msg message) { if(isMessageDispatcherAlive()) { if(message instanceof MessageWithResponse) { MessageWithResponse msgWithResp = (MessageWithResponse) message; try { Msg reply = msgWithResp.replyFromServer(); write(reply); } catch(Db4oRecoverableException exc) { writeException(message, exc); return true; } catch(Throwable t){ t.printStackTrace(); fatalShutDownServer(t); return false; } try { msgWithResp.postProcessAtServer(); return true; } catch(Exception exc) { exc.printStackTrace(); } return true; } try { ((ServerSideMessage)message).processAtServer(); return true; } catch(Db4oRecoverableException exc) { exc.printStackTrace(); return true; } catch(Throwable t){ t.printStackTrace(); fatalShutDownServer(t); } } return false; } private void fatalShutDownServer(Throwable origExc) { new FatalServerShutdown(_server, origExc); } private void writeException(Msg message, Exception exc) { if(!(message instanceof MessageWithResponse)) { exc.printStackTrace(); return; } if(!(exc instanceof RuntimeException)) { exc = new Db4oException(exc); } ensureStackTraceCapture(exc); // Writing exceptions can produce ClassMetadata in // the main ObjectContainer. synchronized (_mainLock) { message.writeException((RuntimeException)exc); } } /** * @sharpen.remove */ private void ensureStackTraceCapture(Exception exc) { exc.printStackTrace(new PrintStream(new OutputStream(){ @Override public void write(int arg0) throws IOException { } })); } private void triggerMessageReceived(Message message) { _messageReceived.trigger(new MessageEventArgs(message)); } public ObjectServerImpl server() { return _server; } public void queryResultFinalized(int queryResultID) { _queryResults.remove(queryResultID); } public void mapQueryResultToID(LazyClientObjectSetStub stub, int queryResultID) { if(_queryResults == null){ _queryResults = new Hashtable4(); } _queryResults.put(queryResultID, stub); } public LazyClientObjectSetStub queryResultForID(int queryResultID){ return (LazyClientObjectSetStub) _queryResults.get(queryResultID); } public void switchToFile(MSwitchToFile message) { synchronized (_mainLock) { String fileName = message.readString(); try { _transactionHandle.releaseTransaction(ShutdownMode.NORMAL); _transactionHandle.acquireTransactionForFile(fileName); write(Msg.OK); } catch (Exception e) { if (Debug4.atHome) { System.out.println("Msg.SWITCH_TO_FILE failed."); e.printStackTrace(); } _transactionHandle.releaseTransaction(ShutdownMode.NORMAL); write(Msg.ERROR); } } } public void switchToMainFile() { synchronized (_mainLock) { _transactionHandle.releaseTransaction(ShutdownMode.NORMAL); write(Msg.OK); } } public void useTransaction(MUseTransaction message) { int threadID = message.readInt(); Transaction transToUse = _server.findTransaction(threadID); _transactionHandle.transaction(transToUse); } public boolean write(Msg msg){ synchronized(_lock) { if(! isMessageDispatcherAlive()){ return false; } return msg.write(_socket); } } public Socket4Adapter socket(){ return _socket; } public String name() { return _clientName; } public void setDispatcherName(String name) { _clientName = name; thread().setName("db4o server message dispatcher " + name); } public int dispatcherID() { return _threadID; } public void login() { _loggedin = true; } public boolean caresAboutCommitted() { return _caresAboutCommitted; } public void caresAboutCommitted(boolean care) { _caresAboutCommitted = true; server().checkCaresAboutCommitted(); } public CallbackObjectInfoCollections committedInfo() { return _committedInfo; } public void dispatchCommitted(CallbackObjectInfoCollections committedInfo) { _committedInfo = committedInfo; } public boolean willDispatchCommitted() { return server().caresAboutCommitted(); } public ClassInfoHelper classInfoHelper() { return server().classInfoHelper(); } /** * EventArgs => MessageEventArgs */ public Event4<MessageEventArgs> messageReceived() { return _messageReceived; } public void join() throws InterruptedException { thread().join(); } private Thread thread() { if (null == _thread) { throw new IllegalStateException(); } return _thread; } }