/** * 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.hdfs2; import java.io.IOException; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.security.auth.login.Configuration; import org.apache.camel.Exchange; import org.apache.camel.Expression; import org.apache.camel.impl.DefaultProducer; import org.apache.camel.util.IOHelper; import org.apache.camel.util.StringHelper; public class HdfsProducer extends DefaultProducer { private final HdfsConfiguration config; private final StringBuilder hdfsPath; private final AtomicBoolean idle = new AtomicBoolean(false); private volatile ScheduledExecutorService scheduler; private volatile HdfsOutputStream ostream; public static final class SplitStrategy { private SplitStrategyType type; private long value; public SplitStrategy(SplitStrategyType type, long value) { this.type = type; this.value = value; } public SplitStrategyType getType() { return type; } public long getValue() { return value; } } public enum SplitStrategyType { BYTES { @Override public boolean split(HdfsOutputStream oldOstream, long value, HdfsProducer producer) { return oldOstream.getNumOfWrittenBytes() >= value; } }, MESSAGES { @Override public boolean split(HdfsOutputStream oldOstream, long value, HdfsProducer producer) { return oldOstream.getNumOfWrittenMessages() >= value; } }, IDLE { @Override public boolean split(HdfsOutputStream oldOstream, long value, HdfsProducer producer) { return producer.idle.get(); } }; public abstract boolean split(HdfsOutputStream oldOstream, long value, HdfsProducer producer); } public HdfsProducer(HdfsEndpoint endpoint, HdfsConfiguration config) { super(endpoint); this.config = config; this.hdfsPath = config.getFileSystemType().getHdfsPath(config); } @Override public HdfsEndpoint getEndpoint() { return (HdfsEndpoint) super.getEndpoint(); } @Override protected void doStart() throws Exception { // need to remember auth as Hadoop will override that, which otherwise means the Auth is broken afterwards Configuration auth = HdfsComponent.getJAASConfiguration(); try { super.doStart(); // setup hdfs if configured to do on startup if (getEndpoint().getConfig().isConnectOnStartup()) { ostream = setupHdfs(true); } SplitStrategy idleStrategy = null; for (SplitStrategy strategy : config.getSplitStrategies()) { if (strategy.type == SplitStrategyType.IDLE) { idleStrategy = strategy; break; } } if (idleStrategy != null) { scheduler = getEndpoint().getCamelContext().getExecutorServiceManager().newSingleThreadScheduledExecutor(this, "HdfsIdleCheck"); log.debug("Creating IdleCheck task scheduled to run every {} millis", config.getCheckIdleInterval()); scheduler.scheduleAtFixedRate(new IdleCheck(idleStrategy), config.getCheckIdleInterval(), config.getCheckIdleInterval(), TimeUnit.MILLISECONDS); } } finally { HdfsComponent.setJAASConfiguration(auth); } } private synchronized HdfsOutputStream setupHdfs(boolean onStartup) throws Exception { if (ostream != null) { return ostream; } StringBuilder actualPath = new StringBuilder(hdfsPath); if (config.getSplitStrategies().size() > 0) { actualPath = newFileName(); } // if we are starting up then log at info level, and if runtime then log at debug level to not flood the log if (onStartup) { log.info("Connecting to hdfs file-system {}:{}/{} (may take a while if connection is not available)", new Object[]{config.getHostName(), config.getPort(), actualPath.toString()}); } else { if (log.isDebugEnabled()) { log.debug("Connecting to hdfs file-system {}:{}/{} (may take a while if connection is not available)", new Object[]{config.getHostName(), config.getPort(), actualPath.toString()}); } } HdfsOutputStream answer = HdfsOutputStream.createOutputStream(actualPath.toString(), config); if (onStartup) { log.info("Connected to hdfs file-system {}:{}/{}", new Object[]{config.getHostName(), config.getPort(), actualPath.toString()}); } else { if (log.isDebugEnabled()) { log.debug("Connected to hdfs file-system {}:{}/{}", new Object[]{config.getHostName(), config.getPort(), actualPath.toString()}); } } return answer; } @Override protected void doStop() throws Exception { super.doStop(); if (scheduler != null) { getEndpoint().getCamelContext().getExecutorServiceManager().shutdown(scheduler); scheduler = null; } if (ostream != null) { IOHelper.close(ostream, "output stream", log); ostream = null; } } @Override public void process(Exchange exchange) throws Exception { // need to remember auth as Hadoop will override that, which otherwise means the Auth is broken afterwards Configuration auth = HdfsComponent.getJAASConfiguration(); try { doProcess(exchange); } finally { HdfsComponent.setJAASConfiguration(auth); } } void doProcess(Exchange exchange) throws Exception { Object body = exchange.getIn().getBody(); Object key = exchange.getIn().getHeader(HdfsHeader.KEY.name()); // if an explicit filename is specified, close any existing stream and append the filename to the hdfsPath if (exchange.getIn().getHeader(Exchange.FILE_NAME) != null) { if (ostream != null) { IOHelper.close(ostream, "output stream", log); } StringBuilder actualPath = getHdfsPathUsingFileNameHeader(exchange); ostream = HdfsOutputStream.createOutputStream(actualPath.toString(), config); } else if (ostream == null) { // must have ostream ostream = setupHdfs(false); } boolean split = false; List<SplitStrategy> strategies = config.getSplitStrategies(); for (SplitStrategy splitStrategy : strategies) { split |= splitStrategy.getType().split(ostream, splitStrategy.value, this); } if (split) { if (ostream != null) { IOHelper.close(ostream, "output stream", log); } StringBuilder actualPath = newFileName(); ostream = HdfsOutputStream.createOutputStream(actualPath.toString(), config); } String path = ostream.getActualPath(); log.trace("Writing body to hdfs-file {}", path); ostream.append(key, body, exchange.getContext().getTypeConverter()); idle.set(false); // close if we do not have idle checker task to do this for us boolean close = scheduler == null; // but user may have a header to explict control the close Boolean closeHeader = exchange.getIn().getHeader(HdfsConstants.HDFS_CLOSE, Boolean.class); if (closeHeader != null) { close = closeHeader; } // if no idle checker then we need to explicit close the stream after usage if (close) { try { HdfsProducer.this.log.trace("Closing stream"); ostream.close(); ostream = null; } catch (IOException e) { // ignore } } log.debug("Wrote body to hdfs-file {}", path); } /** * helper method to construct the hdfsPath from the CamelFileName String or Expression * @param exchange * @return */ private StringBuilder getHdfsPathUsingFileNameHeader(Exchange exchange) { StringBuilder actualPath = new StringBuilder(hdfsPath); String fileName = ""; Object value = exchange.getIn().getHeader(Exchange.FILE_NAME); if (value instanceof String) { fileName = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, value); } else if (value instanceof Expression) { fileName = ((Expression) value).evaluate(exchange, String.class); } return actualPath.append(fileName); } private StringBuilder newFileName() { StringBuilder actualPath = new StringBuilder(hdfsPath); actualPath.append(StringHelper.sanitize(getEndpoint().getCamelContext().getUuidGenerator().generateUuid())); return actualPath; } /** * Idle check background task */ private final class IdleCheck implements Runnable { private final SplitStrategy strategy; private IdleCheck(SplitStrategy strategy) { this.strategy = strategy; } @Override public void run() { // only run if ostream has been created if (ostream == null) { return; } HdfsProducer.this.log.trace("IdleCheck running"); if (System.currentTimeMillis() - ostream.getLastAccess() > strategy.value && !idle.get() && !ostream.isBusy().get()) { idle.set(true); try { HdfsProducer.this.log.trace("Closing stream as idle"); ostream.close(); } catch (IOException e) { // ignore } } } @Override public String toString() { return "IdleCheck"; } } }