/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* 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.uberfire.io.impl.cluster.helix;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.helix.Criteria;
import org.apache.helix.HelixManager;
import org.apache.helix.InstanceType;
import org.apache.helix.NotificationContext;
import org.apache.helix.messaging.handling.HelixTaskResult;
import org.apache.helix.messaging.handling.MessageHandler;
import org.apache.helix.messaging.handling.MessageHandlerFactory;
import org.apache.helix.model.ExternalView;
import org.apache.helix.model.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.uberfire.commons.cluster.ClusterService;
import org.uberfire.commons.data.Pair;
import org.uberfire.commons.lifecycle.PriorityDisposableRegistry;
import org.uberfire.commons.message.AsyncCallback;
import org.uberfire.commons.message.MessageHandlerResolver;
import org.uberfire.commons.message.MessageType;
import org.uberfire.io.impl.cluster.ClusterMessageType;
import static java.util.Arrays.asList;
import static java.util.UUID.randomUUID;
import static org.apache.helix.HelixManagerFactory.getZKHelixManager;
public class ClusterServiceHelix implements ClusterService {
private static final AtomicInteger counter = new AtomicInteger(0);
private static final Logger logger = LoggerFactory.getLogger(ClusterServiceHelix.class);
private static final String ORIGIN = "origin";
private static final String SERVICE_ID = "serviceId";
private static final String OFFLINE = "OFFLINE";
private final String clusterName;
private final String instanceName;
private final HelixManager participantManager;
private final String resourceName;
private final Map<String, MessageHandlerResolver> messageHandlerResolver = new ConcurrentHashMap<String, MessageHandlerResolver>();
private final ReentrantLock lock = new ReentrantLock(true);
public ClusterServiceHelix(final String clusterName,
final String zkAddress,
final String instanceName,
final String resourceName,
final MessageHandlerResolver messageHandlerResolver) {
this.clusterName = clusterName;
this.instanceName = instanceName;
this.resourceName = resourceName;
addMessageHandlerResolver(messageHandlerResolver);
this.participantManager = getZkHelixManager(clusterName,
zkAddress,
instanceName);
PriorityDisposableRegistry.register(this);
start();
}
HelixManager getZkHelixManager(String clusterName,
String zkAddress,
String instanceName) {
return getZKHelixManager(clusterName,
instanceName,
InstanceType.PARTICIPANT,
zkAddress);
}
//TODO {porcelli} quick hack for now, the real solution would have a cluster per repo
@Override
public void addMessageHandlerResolver(final MessageHandlerResolver resolver) {
if (resolver != null) {
this.messageHandlerResolver.put(resolver.getServiceId(),
resolver);
}
}
void start() {
try {
participantManager.getMessagingService().registerMessageHandlerFactory(Message.MessageType.USER_DEFINE_MSG.toString(),
new MessageHandlerResolverWrapper().convert());
participantManager.getStateMachineEngine().registerStateModelFactory("LeaderStandby",
new LockTransitionalFactory());
participantManager.connect();
offlinePartition();
} catch (final Exception ex) {
throw new RuntimeException(ex);
}
}
String getNodeStatus() {
final String partition = resourceName + "_0";
final ExternalView view = getResourceExternalView();
if (clusterIsNotSetYet(view,
partition)) {
return OFFLINE;
}
final Map<String, String> stateMap = view.getStateMap(partition);
return stateMap.get(instanceName);
}
ExternalView getResourceExternalView() {
return participantManager.getClusterManagmentTool().getResourceExternalView(clusterName,
resourceName);
}
private boolean clusterIsNotSetYet(ExternalView view,
String partition) {
//first start with fresh setup
if (view == null) {
return true;
}
final Map<String, String> stateMap = view.getStateMap(partition);
return stateMap == null || stateMap.get(instanceName) == null;
}
@Override
public void dispose() {
if (participantManager != null && participantManager.isConnected()) {
participantManager.disconnect();
}
}
@Override
public void onStart(final Runnable runnable) {
runnable.run();
}
@Override
public int getHoldCount() {
return lock.getHoldCount();
}
private void offlinePartition() {
if (OFFLINE.equals(getNodeStatus())) {
return;
}
participantManager.getClusterManagmentTool().enablePartition(false,
clusterName,
instanceName,
resourceName,
asList(resourceName + "_0"));
while (!OFFLINE.equals(getNodeStatus())) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
}
private void enablePartition() {
if ("LEADER".equals(getNodeStatus())) {
return;
}
participantManager.getClusterManagmentTool().enablePartition(true,
clusterName,
instanceName,
resourceName,
asList(resourceName + "_0"));
while (!"LEADER".equals(getNodeStatus())) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
}
private void disablePartition() {
String nodeStatus = getNodeStatus();
if ("STANDBY".equals(nodeStatus) || OFFLINE.equals(nodeStatus)) {
return;
}
participantManager.getClusterManagmentTool().enablePartition(false,
clusterName,
instanceName,
resourceName,
asList(resourceName + "_0"));
while (!("STANDBY".equals(nodeStatus) || OFFLINE.equals(nodeStatus))) {
try {
Thread.sleep(10);
nodeStatus = getNodeStatus();
} catch (InterruptedException e) {
}
}
}
@Override
public void lock() {
lock.lock();
enablePartition();
}
@Override
public void unlock() {
disablePartition();
lock.unlock();
}
@Override
public void broadcastAndWait(final String serviceId,
final MessageType type,
final Map<String, String> content,
int timeOut) {
participantManager.getMessagingService().sendAndWait(buildCriteria(),
buildMessage(serviceId,
type,
content),
new org.apache.helix.messaging.AsyncCallback(timeOut) {
@Override
public void onTimeOut() {
}
@Override
public void onReplyMessage(final Message message) {
}
},
timeOut);
}
@Override
public void broadcastAndWait(final String serviceId,
final MessageType type,
final Map<String, String> content,
final int timeOut,
final AsyncCallback callback) {
int msg = participantManager.getMessagingService().sendAndWait(buildCriteria(),
buildMessage(serviceId,
type,
content),
new org.apache.helix.messaging.AsyncCallback() {
@Override
public void onTimeOut() {
callback.onTimeOut();
}
@Override
public void onReplyMessage(final Message message) {
final MessageType type = buildMessageTypeFromReply(message);
final Map<String, String> map = getMessageContentFromReply(message);
callback.onReply(type,
map);
}
},
timeOut);
if (msg == 0) {
callback.onTimeOut();
}
}
@Override
public void broadcast(final String serviceId,
final MessageType type,
final Map<String, String> content) {
participantManager.getMessagingService().send(buildCriteria(),
buildMessage(serviceId,
type,
content));
}
@Override
public void broadcast(final String serviceId,
final MessageType type,
final Map<String, String> content,
final int timeOut,
final AsyncCallback callback) {
participantManager.getMessagingService().send(buildCriteria(),
buildMessage(serviceId,
type,
content),
new org.apache.helix.messaging.AsyncCallback() {
@Override
public void onTimeOut() {
callback.onTimeOut();
}
@Override
public void onReplyMessage(final Message message) {
final MessageType type = buildMessageTypeFromReply(message);
final Map<String, String> map = getMessageContent(message);
callback.onReply(type,
map);
}
},
timeOut);
}
@Override
public void sendTo(final String serviceId,
final String resourceId,
final MessageType type,
final Map<String, String> content) {
participantManager.getMessagingService().send(buildCriteria(resourceId),
buildMessage(serviceId,
type,
content));
}
private Criteria buildCriteria(final String resourceId) {
return new Criteria() {{
setInstanceName(resourceId);
setRecipientInstanceType(InstanceType.PARTICIPANT);
setResource(resourceName);
setSelfExcluded(true);
setSessionSpecific(true);
}};
}
private Criteria buildCriteria() {
return buildCriteria("%");
}
private Message buildMessage(final String serviceId,
final MessageType type,
final Map<String, String> content) {
return new Message(Message.MessageType.USER_DEFINE_MSG,
randomUUID().toString()) {{
setMsgState(Message.MessageState.NEW);
getRecord().setMapField("content",
content);
getRecord().setSimpleField(SERVICE_ID,
serviceId);
getRecord().setSimpleField("type",
type.toString());
getRecord().setSimpleField(ORIGIN,
instanceName);
}};
}
@Override
public int priority() {
return Integer.MIN_VALUE + 200;
}
private MessageType buildMessageType(final String _type) {
if (_type == null) {
return null;
}
MessageType type;
try {
type = ClusterMessageType.valueOf(_type);
} catch (Exception ex) {
type = new MessageType() {
@Override
public String toString() {
return _type;
}
@Override
public int hashCode() {
return _type.hashCode();
}
};
}
return type;
}
private MessageType buildMessageTypeFromReply(Message message) {
final Map<String, String> result = message.getRecord().getMapField(Message.Attributes.MESSAGE_RESULT.toString());
return buildMessageType(result.get("type"));
}
private Map<String, String> getMessageContent(final Message message) {
return message.getRecord().getMapField("content");
}
private Map<String, String> getMessageContentFromReply(final Message message) {
return new HashMap<String, String>() {{
for (final Map.Entry<String, String> field : message.getRecord().getMapField(Message.Attributes.MESSAGE_RESULT.toString()).entrySet()) {
if (!field.getKey().equals(SERVICE_ID) && !field.getKey().equals(ORIGIN) && !field.getKey().equals("type")) {
put(field.getKey(),
field.getValue());
}
}
}};
}
class MessageHandlerResolverWrapper {
MessageHandlerFactory convert() {
return new MessageHandlerFactory() {
@Override
public MessageHandler createHandler(final Message message,
final NotificationContext context) {
return new MessageHandler(message,
context) {
@Override
public HelixTaskResult handleMessage() throws InterruptedException {
try {
final String serviceId = _message.getRecord().getSimpleField(SERVICE_ID);
final MessageType type = buildMessageType(_message.getRecord().getSimpleField("type"));
final Map<String, String> map = getMessageContent(_message);
final MessageHandlerResolver resolver = messageHandlerResolver.get(serviceId);
if (resolver == null) {
System.err.println("serviceId not found '" + serviceId + "'");
return new HelixTaskResult() {{
setSuccess(false);
setMessage("Can't find resolver");
}};
}
final org.uberfire.commons.message.MessageHandler handler = resolver.resolveHandler(serviceId,
type);
if (handler == null) {
System.err.println("handler not found for '" + serviceId + "' and type '" + type.toString() + "'");
return new HelixTaskResult() {{
setSuccess(false);
setMessage("Can't find handler.");
}};
}
final Pair<MessageType, Map<String, String>> result = handler.handleMessage(type,
map);
if (result == null) {
return new HelixTaskResult() {{
setSuccess(true);
}};
}
return new HelixTaskResult() {{
setSuccess(true);
getTaskResultMap().put(SERVICE_ID,
serviceId);
getTaskResultMap().put("type",
result.getK1().toString());
getTaskResultMap().put(ORIGIN,
instanceName);
for (Map.Entry<String, String> entry : result.getK2().entrySet()) {
getTaskResultMap().put(entry.getKey(),
entry.getValue());
}
}};
} catch (final Throwable e) {
logger.error("Error while processing cluster message",
e);
return new HelixTaskResult() {{
setSuccess(false);
setMessage(e.getMessage());
setException(new RuntimeException(e));
}};
}
}
@Override
public void onError(final Exception e,
final ErrorCode code,
final ErrorType type) {
}
};
}
@Override
public String getMessageType() {
return Message.MessageType.USER_DEFINE_MSG.toString();
}
@Override
public void reset() {
}
};
}
}
}