/* 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 java.util.*;
import com.db4o.*;
import com.db4o.config.*;
import com.db4o.cs.config.*;
import com.db4o.cs.foundation.*;
import com.db4o.cs.internal.config.*;
import com.db4o.cs.internal.messages.*;
import com.db4o.events.*;
import com.db4o.ext.*;
import com.db4o.foundation.*;
import com.db4o.internal.*;
import com.db4o.internal.config.*;
import com.db4o.internal.events.*;
import com.db4o.internal.threading.*;
import com.db4o.types.*;
public class ObjectServerImpl implements ObjectServerEvents, ObjectServer, ExtObjectServer, Runnable, TransientClass {
private static final int START_THREAD_WAIT_TIMEOUT = 5000;
private final String _name;
private ServerSocket4 _serverSocket;
private int _port;
private int i_threadIDGen = 1;
private final Collection4 _dispatchers = new Collection4();
private LocalObjectContainer _container;
private ClientTransactionPool _transactionPool;
private final Lock4 _startupLock = new Lock4();
private ServerConfigurationImpl _serverConfig;
private BlockingQueue _committedInfosQueue = new BlockingQueue();
private CommittedCallbacksDispatcher _committedCallbacksDispatcher;
private boolean _caresAboutCommitted;
private final Socket4Factory _socketFactory;
private final boolean _isEmbeddedServer;
private final ClassInfoHelper _classInfoHelper;
private final Event4Impl<StringEventArgs> _clientDisconnected = Event4Impl.newInstance();
private final Event4Impl<ClientConnectionEventArgs> _clientConnected = Event4Impl.newInstance();
private final Event4Impl<ServerClosedEventArgs> _closed = Event4Impl.newInstance();
public ObjectServerImpl(final LocalObjectContainer container, ServerConfiguration serverConfig, int port) {
this(container, (ServerConfigurationImpl) serverConfig, (port < 0 ? 0 : port), port == 0);
}
private ObjectServerImpl(final LocalObjectContainer container, ServerConfigurationImpl serverConfig, int port, boolean isEmbeddedServer) {
_isEmbeddedServer = isEmbeddedServer;
_container = container;
_serverConfig = serverConfig;
_socketFactory = serverConfig.networking().socketFactory();
_transactionPool = new ClientTransactionPool(container);
_port = port;
_name = "db4o ServerSocket FILE: " + container.toString() + " PORT:"+ _port;
_container.setServer(true);
configureObjectServer();
_classInfoHelper = new ClassInfoHelper(Db4oClientServerLegacyConfigurationBridge.asLegacy(serverConfig));
_container.classCollection().checkAllClassChanges();
boolean ok = false;
try {
ensureLoadStaticClass();
startCommittedCallbackThread(_committedInfosQueue);
startServer();
if(_serverConfig != null) {
_serverConfig.applyConfigurationItems(this);
}
ok = true;
} finally {
if(!ok) {
close();
}
}
}
private void startServer() {
if (isEmbeddedServer()) {
return;
}
_startupLock.run(new Closure4() { public Object run() {
startServerSocket();
startServerThread();
boolean started=false;
while(!started) {
try {
_startupLock.snooze(START_THREAD_WAIT_TIMEOUT);
started=true;
}
// not specialized to InterruptException for .NET conversion
catch (Exception exc) {
}
}
return null;
}});
}
private void startServerThread() {
_startupLock.run(new Closure4() { public Object run() {
threadPool().start(_name, ObjectServerImpl.this);
return null;
}});
}
private ThreadPool4 threadPool() {
return _container.threadPool();
}
private void startServerSocket() {
try {
_serverSocket = _socketFactory.createServerSocket(_port);
_port = _serverSocket.getLocalPort();
} catch (IOException e) {
throw new Db4oIOException(e);
}
_serverSocket.setSoTimeout(_serverConfig.timeoutServerSocket());
}
private boolean isEmbeddedServer() {
return _isEmbeddedServer;
}
private void ensureLoadStaticClass() {
_container.produceClassMetadata(_container._handlers.ICLASS_STATICCLASS);
}
private void configureObjectServer() {
((CommonConfigurationImpl)_serverConfig.common()).callbackMode(CallBackMode.DELETE_ONLY);
// the minimum activation depth of com.db4o.User.class should be 1.
// Otherwise, we may get null password.
_serverConfig.common().objectClass(User.class).minimumActivationDepth(1);
}
public void backup(String path) throws IOException {
_container.backup(path);
}
final void checkClosed() {
if (_container == null) {
Exceptions4.throwRuntimeException(Messages.CLOSED_OR_OPEN_FAILED, _name);
}
_container.checkClosed();
}
/**
* System.IDisposable.Dispose()
*/
public void dispose() {
close();
}
public synchronized boolean close() {
return close(ShutdownMode.NORMAL);
}
public synchronized boolean close(ShutdownMode mode) {
try {
closeServerSocket();
stopCommittedCallbacksDispatcher();
closeMessageDispatchers(mode);
return closeFile(mode);
}
finally {
triggerClosed();
}
}
private void stopCommittedCallbacksDispatcher() {
if(_committedCallbacksDispatcher != null){
_committedCallbacksDispatcher.stop();
}
}
private boolean closeFile(ShutdownMode mode) {
if (_container != null) {
_transactionPool.close(mode);
_container = null;
}
return true;
}
private void closeMessageDispatchers(ShutdownMode mode) {
Iterator4 i = iterateDispatchers();
while (i.moveNext()) {
try {
((ServerMessageDispatcher) i.current()).close(mode);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public Iterator4 iterateDispatchers() {
synchronized (_dispatchers) {
return new Collection4(_dispatchers).iterator();
}
}
private void closeServerSocket() {
try {
if (_serverSocket != null) {
_serverSocket.close();
}
} catch (Exception e) {
if (Deploy.debug) {
System.out
.println("ObjectServer.close() ServerSocket failed to close.");
}
}
_serverSocket = null;
}
public Configuration configure() {
return Db4oClientServerLegacyConfigurationBridge.asLegacy(_serverConfig);
}
public ExtObjectServer ext() {
return this;
}
private ServerMessageDispatcherImpl findThread(int a_threadID) {
synchronized (_dispatchers) {
Iterator4 i = _dispatchers.iterator();
while (i.moveNext()) {
ServerMessageDispatcherImpl serverThread = (ServerMessageDispatcherImpl) i.current();
if (serverThread._threadID == a_threadID) {
return serverThread;
}
}
}
return null;
}
Transaction findTransaction(int threadID) {
ServerMessageDispatcherImpl dispatcher = findThread(threadID);
return (dispatcher == null ? null : dispatcher.transaction());
}
public synchronized void grantAccess(String userName, String password) {
checkClosed();
synchronized (_container.lock()) {
User existing = getUser(userName);
if (existing != null) {
setPassword(existing, password);
} else {
addUser(userName, password);
}
_container.commit();
}
}
private void addUser(String userName, String password) {
_container.store(new User(userName, password));
}
private void setPassword(User existing, String password) {
existing.password = password;
_container.store(existing);
}
public User getUser(String userName) {
final ObjectSet result = queryUsers(userName);
if (!result.hasNext()) {
return null;
}
return (User) result.next();
}
private ObjectSet queryUsers(String userName) {
_container.showInternalClasses(true);
try {
return _container.queryByExample(new User(userName, null));
} finally {
_container.showInternalClasses(false);
}
}
public ObjectContainer objectContainer() {
return _container;
}
public synchronized ObjectContainer openClient() {
checkClosed();
synchronized (_container.lock()) {
return new ObjectContainerSession(_container);
}
}
void removeThread(ServerMessageDispatcherImpl dispatcher) {
synchronized (_dispatchers) {
_dispatchers.remove(dispatcher);
checkCaresAboutCommitted();
}
triggerClientDisconnected(dispatcher.name());
}
public synchronized void revokeAccess(String userName) {
checkClosed();
synchronized (_container.lock()) {
deleteUsers(userName);
_container.commit();
}
}
private void deleteUsers(String userName) {
ObjectSet set = queryUsers(userName);
while (set.hasNext()) {
_container.delete(set.next());
}
}
public void run() {
logListeningOnPort();
notifyThreadStarted();
listen();
}
private void startCommittedCallbackThread(BlockingQueue committedInfosQueue) {
if(isEmbeddedServer()) {
return;
}
_committedCallbacksDispatcher = new CommittedCallbacksDispatcher(this, committedInfosQueue);
threadPool().start("Server commit callback dispatcher thread", _committedCallbacksDispatcher);
}
private void listen() {
// we are keeping a reference to container to avoid race conditions upon closing this server
final LocalObjectContainer threadContainer = _container;
while (_serverSocket != null) {
threadContainer.withEnvironment(new Runnable() { public void run() {
try {
Socket4 socket = _serverSocket.accept();
ServerMessageDispatcherImpl messageDispatcher =
new ServerMessageDispatcherImpl(
ObjectServerImpl.this,
new ClientTransactionHandle(_transactionPool),
socket,
newThreadId(),
false,
threadContainer.lock());
addServerMessageDispatcher(messageDispatcher);
threadPool().start("server message dispatcher (still initializing)", messageDispatcher);
} catch (Exception e) {
// CatchAll because we can get expected timeout exceptions
// although we still want to continue to use the ServerSocket.
// No nice way to catch a specific exception because
// SocketTimeOutException is JDK 1.4 and above.
//e.printStackTrace();
}
}});
}
}
private void triggerClientConnected(ServerMessageDispatcher messageDispatcher) {
_clientConnected.trigger(new ClientConnectionEventArgs(messageDispatcher));
}
private void triggerClientDisconnected(String clientName) {
_clientDisconnected.trigger(new StringEventArgs(clientName));
}
private void triggerClosed() {
_closed.trigger(new ServerClosedEventArgs());
}
private void notifyThreadStarted() {
_startupLock.run(new Closure4() { public Object run() {
_startupLock.awake();
return null;
}});
}
private void logListeningOnPort() {
_container.logMsg(Messages.SERVER_LISTENING_ON_PORT, "" + _serverSocket.getLocalPort());
}
private int newThreadId() {
return i_threadIDGen++;
}
private void addServerMessageDispatcher(ServerMessageDispatcher dispatcher) {
synchronized (_dispatchers) {
_dispatchers.add(dispatcher);
checkCaresAboutCommitted();
}
triggerClientConnected(dispatcher);
}
public void addCommittedInfoMsg(MCommittedInfo message) {
_committedInfosQueue.add(message);
}
public void broadcastReplicationCommit(long timestamp, List concurrentTimestamps) {
Iterator4 i = iterateDispatchers();
while(i.moveNext()){
ServerMessageDispatcher dispatcher = (ServerMessageDispatcher) i.current();
LocalTransaction transaction = (LocalTransaction) dispatcher.transaction();
transaction.notifyAboutOtherReplicationCommit(timestamp, concurrentTimestamps);
}
}
public void broadcastMsg(Msg message, BroadcastFilter filter) {
Iterator4 i = iterateDispatchers();
while(i.moveNext()){
ServerMessageDispatcher dispatcher = (ServerMessageDispatcher) i.current();
if(filter.accept(dispatcher)) {
dispatcher.write(message);
}
}
}
public boolean caresAboutCommitted(){
return _caresAboutCommitted;
}
public void checkCaresAboutCommitted(){
_caresAboutCommitted = anyDispatcherCaresAboutCommitted();
}
private boolean anyDispatcherCaresAboutCommitted() {
Iterator4 i = iterateDispatchers();
while(i.moveNext()){
ServerMessageDispatcher dispatcher = (ServerMessageDispatcher) i.current();
if(dispatcher.caresAboutCommitted()){
return true;
}
}
return false;
}
public int port() {
return _port;
}
public int clientCount(){
synchronized(_dispatchers){
return _dispatchers.size();
}
}
public ClassInfoHelper classInfoHelper() {
return _classInfoHelper;
}
public Event4<ClientConnectionEventArgs> clientConnected() {
return _clientConnected;
}
public Event4<StringEventArgs> clientDisconnected() {
return _clientDisconnected;
}
public Event4<ServerClosedEventArgs> closed() {
return _closed;
}
void withEnvironment(Runnable runnable) {
_container.withEnvironment(runnable);
}
public int transactionCount() {
return _transactionPool.openTransactionCount();
}
}