//
// Copyright 2010 Cinch Logic Pty Ltd.
//
// http://www.chililog.com
//
// 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 org.chililog.server.pubsub.jsonhttp;
import java.util.UUID;
import org.apache.commons.lang.StringUtils;
import org.chililog.server.common.ChiliLogException;
import org.chililog.server.common.JsonTranslator;
import org.chililog.server.common.Log4JLogger;
import org.chililog.server.data.MongoConnection;
import org.chililog.server.data.RepositoryConfigBO;
import org.chililog.server.data.RepositoryConfigController;
import org.chililog.server.data.UserBO;
import org.chililog.server.data.UserController;
import org.chililog.server.engine.MqService;
import org.chililog.server.engine.RepositoryEntryMqMessage;
import org.chililog.server.pubsub.Strings;
import org.chililog.server.pubsub.websocket.TextWebSocketFrame;
import org.chililog.server.workbench.workers.AuthenticationTokenAO;
import org.hornetq.api.core.client.ClientConsumer;
import org.hornetq.api.core.client.ClientMessage;
import org.hornetq.api.core.client.ClientSession;
import org.hornetq.api.core.client.MessageHandler;
import org.jboss.netty.channel.Channel;
import com.mongodb.DB;
/**
* Worker to process subscription requests
*/
public class SubscriptionWorker {
private static Log4JLogger _logger = Log4JLogger.getLogger(SubscriptionWorker.class);
private Channel _channel = null;
private ClientSession _session = null;
private ClientConsumer _consumer = null;
/**
* Constructor
*
* @param channel
* Netty channel to write to
*/
public SubscriptionWorker(Channel channel) {
_channel = channel;
}
/**
* Process a publishing request
*
* @param request
* Publishing request in JSON format
* @param response
* Publishing response in JSON format
* @return true if successful; false if error
*/
public boolean process(String request, StringBuilder response) {
String messageID = null;
try {
if (StringUtils.isBlank(request)) {
throw new IllegalArgumentException("Request content is blank.");
}
// Parse JSON
SubscriptionRequestAO requestAO = JsonTranslator.getInstance().fromJson(request,
SubscriptionRequestAO.class);
messageID = requestAO.getMessageID();
// Authenticate
authenticate(requestAO);
// Subscribe using system user because sometimes a token is supplied from the workbench
String queueAddress = RepositoryConfigBO.buildPubSubAddress(requestAO.getRepositoryName());
String queueName = queueAddress + ".json-http-" + _channel.getId() + "." + UUID.randomUUID().toString();
_session = MqService.getInstance().getNonTransactionalSystemClientSession();
// Filter messages
StringBuilder filter = new StringBuilder();
if (!StringUtils.isBlank(requestAO.getHost())) {
filter.append(String.format("%s = '%s'", RepositoryEntryMqMessage.HOST, requestAO.getHost()));
}
if (!StringUtils.isBlank(requestAO.getSource())) {
if (filter.length() > 0) {
filter.append(" AND ");
}
filter.append(String.format("%s = '%s'", RepositoryEntryMqMessage.SOURCE, requestAO.getSource()));
}
if (!StringUtils.isBlank(requestAO.getSeverity())) {
if (filter.length() > 0) {
filter.append(" AND ");
}
filter.append(String.format("%s IN (", RepositoryEntryMqMessage.SEVERITY));
int sev = Integer.parseInt(requestAO.getSeverity());
for (int i = 0; i <= sev; i++) {
filter.append(String.format("'%s', ", i));
}
filter.replace(filter.length() - 1, filter.length(), ")"); // replace last comma with end )
}
if (filter.length() == 0) {
_session.createTemporaryQueue(queueAddress, queueName);
} else {
_logger.debug("Subscription filter %s", filter);
_session.createTemporaryQueue(queueAddress, queueName, filter.toString());
}
_consumer = _session.createConsumer(queueName);
MqMessageHandler handler = new MqMessageHandler(_channel, messageID);
_consumer.setMessageHandler(handler);
_session.start();
// Prepare response
SubscriptionResponseAO responseAO = new SubscriptionResponseAO(messageID);
JsonTranslator.getInstance().toJson(responseAO, response);
// Finish
return true;
} catch (Exception ex) {
_logger.error(ex, "Error processing message: %s", request);
SubscriptionResponseAO responseAO = new SubscriptionResponseAO(messageID, ex);
JsonTranslator.getInstance().toJson(responseAO, response);
return false;
}
}
/**
* Stop subscription
*/
public void stop() {
try {
if (_consumer != null) {
_consumer.close();
}
if (_session != null) {
_session.close();
}
} catch (Exception ex) {
_logger.error(ex, "Error stopping subscription");
}
}
/**
* Authenticate request
*
* @param subscriptionAO
* @throws ChiliLogException
*/
public void authenticate(SubscriptionRequestAO subscriptionAO) throws ChiliLogException {
String repoName = subscriptionAO.getRepositoryName();
// Check db
DB db = MongoConnection.getInstance().getConnection();
// Make user repository exists
RepositoryConfigController.getInstance().getByName(db, repoName);
// Check user
UserBO user = UserController.getInstance().getByUsername(db, subscriptionAO.getUsername());
boolean passwordOK = false;
if (subscriptionAO.getPassword().startsWith("token:")) {
// Password is a token so we need to check the token
// Must have come from the workbench
String jsonToken = subscriptionAO.getPassword().substring(6);
AuthenticationTokenAO token = AuthenticationTokenAO.fromString(jsonToken);
passwordOK = token.getUserID().equals(user.getDocumentID().toString());
} else {
// Make sure user exists and password is valid
passwordOK = user.validatePassword(subscriptionAO.getPassword());
}
if (!passwordOK) {
throw new ChiliLogException(Strings.SUBSCRIBER_AUTHENTICATION_ERROR);
}
// Make sure the user can publish to the repository
String administratorRole = UserBO.createRepositoryAdministratorRoleName(repoName);
String workbenchRole = UserBO.createRepositoryWorkbenchRoleName(repoName);
String subscriptionRole = UserBO.createRepositorySubscriberRoleName(repoName);
if (!user.hasRole(administratorRole) && !user.hasRole(subscriptionRole) && !user.hasRole(workbenchRole)
&& !user.isSystemAdministrator()) {
throw new ChiliLogException(Strings.PUBLISHER_AUTHENTICATION_ERROR, subscriptionAO.getUsername(), repoName);
}
}
/**
* Class to handle incoming log messages
*/
public static class MqMessageHandler implements MessageHandler {
private static Log4JLogger _logger = Log4JLogger.getLogger(MqMessageHandler.class);
private String _messageID = null;
private Channel _channel = null;
/**
* Constructor
*
* @param channel
* Netty channel to write messages into
*/
public MqMessageHandler(Channel channel, String messageID) {
_messageID = messageID;
_channel = channel;
}
/**
* When a message is received, pass it on to the client
*/
@Override
public void onMessage(ClientMessage message) {
try {
if (!_channel.isOpen() || !_channel.isConnected()) {
return;
}
LogEntryAO logEntry = new LogEntryAO();
logEntry.setTimestamp(message.getStringProperty(RepositoryEntryMqMessage.TIMESTAMP));
logEntry.setSource(message.getStringProperty(RepositoryEntryMqMessage.SOURCE));
logEntry.setHost(message.getStringProperty(RepositoryEntryMqMessage.HOST));
logEntry.setSeverity(message.getStringProperty(RepositoryEntryMqMessage.SEVERITY));
logEntry.setMessage(message.getBodyBuffer().readNullableSimpleString().toString());
SubscriptionResponseAO responseAO = new SubscriptionResponseAO(_messageID, logEntry);
String responseJson = JsonTranslator.getInstance().toJson(responseAO);
_logger.debug("Handling message: %s", responseJson);
_channel.write(new TextWebSocketFrame(responseJson));
return;
} catch (Exception ex) {
_logger.error(ex, "Error forwarding subscription message JSON HTTP web socket client");
}
}
}
}