/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.camel.component.zookeeper;
import static java.lang.String.format;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.component.zookeeper.operations.CreateOperation;
import org.apache.camel.component.zookeeper.operations.DeleteOperation;
import org.apache.camel.component.zookeeper.operations.GetChildrenOperation;
import org.apache.camel.component.zookeeper.operations.OperationResult;
import org.apache.camel.component.zookeeper.operations.SetDataOperation;
import org.apache.camel.impl.DefaultProducer;
import org.apache.camel.util.ExchangeHelper;
import org.apache.zookeeper.AsyncCallback.StatCallback;
import org.apache.zookeeper.AsyncCallback.VoidCallback;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException.Code;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import static org.apache.camel.component.zookeeper.ZooKeeperUtils.getAclListFromMessage;
import static org.apache.camel.component.zookeeper.ZooKeeperUtils.getCreateMode;
import static org.apache.camel.component.zookeeper.ZooKeeperUtils.getCreateModeFromString;
import static org.apache.camel.component.zookeeper.ZooKeeperUtils.getNodeFromMessage;
import static org.apache.camel.component.zookeeper.ZooKeeperUtils.getPayloadFromExchange;
import static org.apache.camel.component.zookeeper.ZooKeeperUtils.getVersionFromMessage;
/**
* <code>ZooKeeperProducer</code> attempts to set the content of nodes in the
* {@link ZooKeeper} cluster with the payloads of the of the exchanges it
* receives.
*/
@SuppressWarnings("rawtypes")
public class ZooKeeperProducer extends DefaultProducer {
public static final String ZK_OPERATION_WRITE = "WRITE";
public static final String ZK_OPERATION_DELETE = "DELETE";
private final ZooKeeperConfiguration configuration;
private ZooKeeperConnectionManager zkm;
private ZooKeeper connection;
public ZooKeeperProducer(ZooKeeperEndpoint endpoint) {
super(endpoint);
this.configuration = endpoint.getConfiguration();
this.zkm = endpoint.getConnectionManager();
}
public void process(Exchange exchange) throws Exception {
if (connection == null) {
connection = this.zkm.getConnection();
}
ProductionContext context = new ProductionContext(connection, exchange);
String operation = exchange.getIn().getHeader(ZooKeeperMessage.ZOOKEEPER_OPERATION, String.class);
boolean isDelete = ZK_OPERATION_DELETE.equals(operation);
if (ExchangeHelper.isOutCapable(exchange)) {
if (isDelete) {
if (log.isDebugEnabled()) {
log.debug(format("Deleting znode '%s', waiting for confirmation", context.node));
}
OperationResult result = synchronouslyDelete(context);
if (configuration.isListChildren()) {
result = listChildren(context);
}
updateExchangeWithResult(context, result);
} else {
if (log.isDebugEnabled()) {
log.debug(format("Storing data to znode '%s', waiting for confirmation", context.node));
}
OperationResult result = synchronouslySetData(context);
if (configuration.isListChildren()) {
result = listChildren(context);
}
updateExchangeWithResult(context, result);
}
} else {
if (isDelete) {
asynchronouslyDeleteNode(connection, context);
} else {
asynchronouslySetDataOnNode(connection, context);
}
}
}
@Override
protected void doStart() throws Exception {
connection = zkm.getConnection();
if (log.isTraceEnabled()) {
log.trace(String.format("Starting zookeeper producer of '%s'", configuration.getPath()));
}
}
@Override
protected void doStop() throws Exception {
super.doStop();
if (log.isTraceEnabled()) {
log.trace(String.format("Shutting down zookeeper producer of '%s'", configuration.getPath()));
}
zkm.shutdown();
}
private void asynchronouslyDeleteNode(ZooKeeper connection, ProductionContext context) {
if (log.isDebugEnabled()) {
log.debug(format("Deleting node '%s', not waiting for confirmation", context.node));
}
connection.delete(context.node, context.version, new AsyncDeleteCallback(), context);
}
private void asynchronouslySetDataOnNode(ZooKeeper connection, ProductionContext context) {
if (log.isDebugEnabled()) {
log.debug(format("Storing data to node '%s', not waiting for confirmation", context.node));
}
connection.setData(context.node, context.payload, context.version, new AsyncSetDataCallback(), context);
}
private void updateExchangeWithResult(ProductionContext context, OperationResult result) {
ZooKeeperMessage out = new ZooKeeperMessage(context.node, result.getStatistics(), context.in.getHeaders());
if (result.isOk()) {
out.setBody(result.getResult());
} else {
context.exchange.setException(result.getException());
}
context.exchange.setOut(out);
}
private OperationResult listChildren(ProductionContext context) throws Exception {
return new GetChildrenOperation(context.connection, configuration.getPath()).get();
}
/** Simple container to avoid passing all these around as parameters */
private class ProductionContext {
ZooKeeper connection;
Exchange exchange;
Message in;
byte[] payload;
int version;
String node;
ProductionContext(ZooKeeper connection, Exchange exchange) {
this.connection = connection;
this.exchange = exchange;
this.in = exchange.getIn();
this.node = getNodeFromMessage(in, configuration.getPath());
this.version = getVersionFromMessage(in);
this.payload = getPayloadFromExchange(exchange);
}
}
private class AsyncSetDataCallback implements StatCallback {
public void processResult(int rc, String node, Object ctx, Stat statistics) {
if (Code.NONODE.equals(Code.get(rc))) {
if (configuration.isCreate()) {
log.warn(format("Node '%s' did not exist, creating it...", node));
ProductionContext context = (ProductionContext)ctx;
OperationResult<String> result = null;
try {
result = createNode(context);
} catch (Exception e) {
log.error(format("Error trying to create node '%s'", node), e);
}
if (result == null || !result.isOk()) {
log.error(format("Error creating node '%s'", node), result.getException());
}
}
} else {
logStoreComplete(node, statistics);
}
}
}
private class AsyncDeleteCallback implements VoidCallback {
@Override
public void processResult(int rc, String path, Object ctx) {
if (log.isDebugEnabled()) {
if (log.isTraceEnabled()) {
log.trace(format("Removed data node '%s'", path));
} else {
log.debug(format("Removed data node '%s'", path));
}
}
}
}
private OperationResult<String> createNode(ProductionContext ctx) throws Exception {
CreateOperation create = new CreateOperation(ctx.connection, ctx.node);
create.setPermissions(getAclListFromMessage(ctx.exchange.getIn()));
CreateMode mode = null;
String modeString = configuration.getCreateMode();
if (modeString != null) {
try {
mode = getCreateModeFromString(modeString, CreateMode.EPHEMERAL);
} catch (Exception e) { }
} else {
mode = getCreateMode(ctx.exchange.getIn(), CreateMode.EPHEMERAL);
}
create.setCreateMode(mode == null ? CreateMode.EPHEMERAL : mode);
create.setData(ctx.payload);
return create.get();
}
/**
* Tries to set the data first and if a no node error is received then an
* attempt will be made to create it instead.
*/
private OperationResult synchronouslySetData(ProductionContext ctx) throws Exception {
SetDataOperation setData = new SetDataOperation(ctx.connection, ctx.node, ctx.payload);
setData.setVersion(ctx.version);
OperationResult result = setData.get();
if (!result.isOk() && configuration.isCreate() && result.failedDueTo(Code.NONODE)) {
log.warn(format("Node '%s' did not exist, creating it.", ctx.node));
result = createNode(ctx);
}
return result;
}
private OperationResult synchronouslyDelete(ProductionContext ctx) throws Exception {
DeleteOperation setData = new DeleteOperation(ctx.connection, ctx.node);
setData.setVersion(ctx.version);
OperationResult result = setData.get();
if (!result.isOk() && configuration.isCreate() && result.failedDueTo(Code.NONODE)) {
log.warn(format("Node '%s' did not exist, creating it.", ctx.node));
result = createNode(ctx);
}
return result;
}
private void logStoreComplete(String path, Stat statistics) {
if (log.isDebugEnabled()) {
if (log.isTraceEnabled()) {
log.trace(format("Stored data to node '%s', and receive statistics %s", path, statistics));
} else {
log.debug(format("Stored data to node '%s'", path));
}
}
}
}