/*
* Copyright [2012] [ShopWiki]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.shopwiki.roger.rpc;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executors;
import com.rabbitmq.client.*;
import com.shopwiki.roger.RabbitConnector;
import com.shopwiki.roger.RabbitReconnector;
import com.shopwiki.roger.RabbitReconnector.*;
import com.shopwiki.roger.util.TimeUtil;
/**
* The main entry point for creating & starting an RPC server using Roger.
*
* @author rstewart
*/
public class RpcServer {
/**
* Used by {@link RpcServer} to create {@link RpcWorker}s.
*
* A standard implementation is provided: {@link BasicWorkerFactory}.
* However, the user may create their own implementation if they
* desire more control over how {@link Channel}s are allocated to {@link RpcWorker}s.
*/
public interface WorkerFactory {
Connection createConnection() throws IOException;
List<RpcWorker> createWorkers(Connection conn, String queuePrefix) throws IOException;
}
// TODO: Rename this QueueManager ???
// TODO: Provide a standard implementation similar to ShopWiki's ???
/**
* No implementation is required.
* You only need to provide one if you want an {@link RpcServer} to programmatically
* create/check your RPC queues & routing-key bindings.
*/
public static interface QueueDeclarator {
void declareQueue(Channel channel, RpcWorker worker) throws IOException;
void bindQueue(Channel channel, RpcWorker worker) throws IOException;
}
private final WorkerFactory workerFactory;
public final String queuePrefix;
private final QueueDeclarator queueDeclarator;
private final PostProcessors postProcessors;
private final RabbitReconnector reconnector;
/**
* @param workerFactory
* @param queuePrefix
*/
public RpcServer(WorkerFactory workerFactory, String queuePrefix) {
this(workerFactory, queuePrefix, null, null, null);
}
/**
* @param workerFactory
* @param queuePrefix
* @param queueDeclarator
* @param postProcessors
* @param reconnectLogger
*/
public RpcServer(WorkerFactory workerFactory, String queuePrefix, QueueDeclarator queueDeclarator, PostProcessors postProcessors, ReconnectLogger reconnectLogger) {
this.workerFactory = workerFactory;
this.queuePrefix = queuePrefix;
this.queueDeclarator = queueDeclarator;
this.postProcessors = postProcessors;
ReconnectHandler reconnectHandler = new ReconnectHandler() {
@Override
public boolean reconnect() throws Exception {
return _start();
}
};
int secondsBeforeReconnect = 1; // TODO: Make this configurable ???
reconnector = new RabbitReconnector(reconnectHandler, reconnectLogger, secondsBeforeReconnect);
}
/**
* Creates {@link RpcWorker}s using the {@link WorkerFactory} provided to the constructor.
* Declares queues & binds routing-keys if a {@link QueueDeclarator} was provided to the constructor.
* Starts each {@link RpcWorker}.
*
* If this fails for any reason, it will periodically attempt to start in a background thread.
*/
public void start() {
if (_start() == false) {
Executors.newSingleThreadExecutor().execute(reconnector);
}
}
private volatile Connection conn = null;
private boolean _start() {
try {
System.out.print(TimeUtil.now() + " Starting RpcServer: ");
conn = workerFactory.createConnection();
List<RpcWorker> workers = workerFactory.createWorkers(conn, queuePrefix);
System.out.println(conn + " with " + workers.size() + " workers.");
Channel channel = conn.createChannel();
if (queueDeclarator != null) {
for (RpcWorker worker : workers) {
queueDeclarator.declareQueue(channel, worker);
queueDeclarator.bindQueue(channel, worker);
}
}
for (RpcWorker worker : workers) {
worker.setPostProcessors(postProcessors);
String queueName = worker.getQueueName();
System.out.println("\t" + "Starting RpcWorker for queue: " + queueName);
channel.queueDeclarePassive(queueName); // make sure the handler's queue exists
worker.start();
}
channel.close();
conn.addShutdownListener(reconnector);
return true;
} catch (Throwable t) {
System.err.println(TimeUtil.now() + " ERROR starting RpcServer:");
t.printStackTrace();
stop();
return false;
}
}
/**
* Closes the connection to RabbitMQ and prevents further automatic restarts.
*/
public void stop() {
System.out.println(TimeUtil.now() + " Stopping RpcServer: " + conn);
RabbitConnector.closeConnectionAndRemoveReconnector(conn, reconnector);
conn = null;
}
}