All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.osinka.camel.beanstalk.BeanstalkConsumer Maven / Gradle / Ivy

There is a newer version: 1.7.0
Show newest version
/**
 * Copyright (C) 2010 Osinka 
 *
 * 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.osinka.camel.beanstalk;

import com.osinka.camel.beanstalk.processors.*;
import com.surftools.BeanstalkClient.BeanstalkException;
import com.surftools.BeanstalkClient.Client;
import com.surftools.BeanstalkClient.Job;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Processor;
import org.apache.camel.spi.Synchronization;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.impl.ScheduledPollConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * PollingConsumer to read Beanstalk jobs.
 *
 * The consumer may delete the job immediately or based on successful {@link Exchange}
 * completion. The behavior is configurable by consumer.awaitJob
 * flag (by default true)
 *
 * This consumer will add a {@link Synchronization} object to every {@link Exchange}
 * object it creates in order to react on successful exchange completion or failure.
 *
 * In the case of successful completion, Beanstalk's delete method is
 * called upon the job. In the case of failure the default reaction is to call
 * bury.
 *
 * The reaction on failures is configurable: possible variants are "bury", "release" or "delete"
 *
 * @author Alexander Azarov
 */
public class BeanstalkConsumer extends ScheduledPollConsumer {
    private final transient Logger log = LoggerFactory.getLogger(getClass());

    String onFailure = BeanstalkComponent.COMMAND_BURY;
    boolean useBlockIO = true;
    boolean deleteImmediately = false;

    private Client client = null;
    private ExecutorService executor = null;
    private Synchronization sync = null;

    private static String[] statsKeysStr = new String[] {"tube", "state"};
    private static String[] statsKeysInt = new String[] {"age", "time-left", "timeouts", "releases", "buries", "kicks"};

    private final Runnable initTask = new Runnable() {
            @Override
            public void run() {
                client = getEndpoint().getConnection().newReadingClient(useBlockIO);
            }
        };
    private final Callable pollTask = new Callable() {
        final Integer NO_WAIT = Integer.valueOf(0);
        
        @Override
        public Exchange call() throws Exception {
            if (client == null)
                throw new RuntimeCamelException("Beanstalk client not initialized");

            try {
                final Job job = client.reserve(NO_WAIT);
                if (job == null)
                    return null;

                if (log.isDebugEnabled())
                    log.debug(String.format("Received job ID %d (data length %d)", job.getJobId(), job.getData().length));

                final Exchange exchange = getEndpoint().createExchange(ExchangePattern.InOnly);
                exchange.setProperty(Headers.JOB_ID, job.getJobId());
                exchange.getIn().setBody(job.getData(), byte[].class);

                Map jobStats = client.statsJob(job.getJobId());
                if (jobStats != null) {
                    for (String key : statsKeysStr) {
                      if (jobStats.containsKey(key))
                          exchange.setProperty(Headers.PREFIX+key, jobStats.get(key).trim());
                    }

                    if (jobStats.containsKey("pri"))
                        exchange.setProperty(Headers.PRIORITY, Long.parseLong(jobStats.get("pri").trim()));

                    for (String key : statsKeysInt) {
                      if (jobStats.containsKey(key))
                          exchange.setProperty(Headers.PREFIX+key, Integer.parseInt(jobStats.get(key).trim()));
                    }
                }

                if (deleteImmediately)
                    client.delete(job.getJobId());
                else
                    exchange.addOnCompletion(sync);

                return exchange;
            } catch (BeanstalkException e) {
                log.error("Beanstalk client error", e);
                resetClient();
                return null;
            }
        }

    };

    public BeanstalkConsumer(final BeanstalkEndpoint endpoint, final Processor processor) {
        super(endpoint, processor);
    }

    @Override
    protected int poll() throws Exception {
        int messagesPolled = 0;
        while (isPollAllowed()) {
            final Exchange exchange = executor.submit(pollTask).get();
            if (exchange == null)
                break;

            ++messagesPolled;
            getProcessor().process(exchange);
        }
        return messagesPolled;
    }

    public String getOnFailure() {
        return onFailure;
    }

    public void setOnFailure(String onFailure) {
        this.onFailure = onFailure;
    }

    public boolean getUseBlockIO() {
        return useBlockIO;
    }

    public void setUseBlockIO(boolean useBlockIO) {
        this.useBlockIO = useBlockIO;
    }

    public boolean getAwaitJob() {
        return !deleteImmediately;
    }

    public void setAwaitJob(boolean awaitingCompletion) {
        this.deleteImmediately = !awaitingCompletion;
    }

    @Override
    public BeanstalkEndpoint getEndpoint() {
        return (BeanstalkEndpoint) super.getEndpoint();
    }

    @Override
    protected void doStart() throws Exception {
        executor = getEndpoint().getCamelContext().getExecutorServiceManager().newSingleThreadExecutor(this, "Beanstalk-Consumer");
        executor.execute(initTask);
        sync = new Sync();
        super.doStart();
    }

    @Override
    protected void doStop() throws Exception {
        super.doStop();
        if (executor != null)
          executor.shutdown();
    }

    protected void resetClient() {
        if (client != null)
            client.close();
        initTask.run();
    }

    class Sync implements Synchronization {
        protected final Command successCommand;
        protected final Command failureCommand;

        public Sync() {
            successCommand = new DeleteCommand(getEndpoint());

            if (BeanstalkComponent.COMMAND_BURY.equals(onFailure))
                failureCommand = new BuryCommand(getEndpoint());
            else if (BeanstalkComponent.COMMAND_RELEASE.equals(onFailure))
                failureCommand = new ReleaseCommand(getEndpoint());
            else if (BeanstalkComponent.COMMAND_DELETE.equals(onFailure))
                failureCommand = new DeleteCommand(getEndpoint());
            else
                throw new IllegalArgumentException(String.format("Unknown failure command: %s", onFailure));
        }

        @Override
        public void onComplete(final Exchange exchange) {
            try {
                executor.submit(new RunCommand(successCommand, exchange)).get();
            } catch (Exception e) {
                if (log.isErrorEnabled())
                    log.error(String.format("Could not run completion of exchange %s", exchange), e);
            }
        }

        @Override
        public void onFailure(final Exchange exchange) {
            try {
                executor.submit(new RunCommand(failureCommand, exchange)).get();
            } catch (Exception e) {
                if (log.isErrorEnabled())
                    log.error(String.format("%s could not run failure of exchange %s", failureCommand.getClass().getName(), exchange), e);
            }
        }

        class RunCommand implements Runnable {
            private final Command command;
            private final Exchange exchange;

            public RunCommand(final Command command, final Exchange exchange) {
                this.command = command;
                this.exchange = exchange;
            }

            @Override
            public void run() {
                try {
                    try {
                        command.act(client, exchange);
                    } catch (BeanstalkException e) {
                        if (log.isWarnEnabled())
                            log.warn(String.format("Post-processing %s of exchange %s failed, retrying.", command.getClass().getName(), exchange), e);
                        resetClient();
                        command.act(client, exchange);
                    }
                } catch (final Exception e) {
                    if (log.isErrorEnabled())
                        log.error(String.format("%s could not post-process exchange %s", command.getClass().getName(), exchange), e);
                    exchange.setException(e);
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy