/* * 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.synapse.transport.pipe; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.CountDownLatch; import org.apache.axis2.transport.base.datagram.DatagramDispatcherCallback; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * {@link Runnable} that reads messages from a given UNIX pipe. * <p> * The pipe will be opened in read/write mode. There are several reasons to * do this: * <ul> * <li>Opening a pipe in read only mode blocks until the other end of the pipe is opened for writing * (see <a href="http://linux.die.net/man/7/fifo"><tt>man 7 fifo</tt></a>). Since there is no * way to cleanly stop a thread blocking in the constructor of {@link FileInputStream} we open * the pipe in read/write mode to avoid blocking.</li> * <li>A pipe opened in read only mode will be closed when the other end is closed. By opening the pipe * in read/write mode we avoid this. If we unexpectedly receive an end-of-file, we shut down the * listener. This avoids unexpected behavior if the file system object is not a pipe (there is * no reliable way in Java to determine this).</li> * <li>Read operations on the pipe are blocking. By opening the pipe in read/write mode we have a * simple way to wake up the listener thread to shut it down cleanly. However, since read/write * operations on a file channel can't be invoked concurrently from different threads, * we need to create two separate channels from the same file descriptor.</li> * </ul> */ public class PipeEndpointListener implements Runnable { private static final Log log = LogFactory.getLog(PipeEndpointListener.class); private final PipeEndpoint endpoint; private final DatagramDispatcherCallback callback; private final RandomAccessFile pipe; private final FileChannel readChannel; private final FileChannel writeChannel; private final Object guard = new Object(); private boolean running; private final CountDownLatch done = new CountDownLatch(1); public PipeEndpointListener(PipeEndpoint endpoint, DatagramDispatcherCallback callback) throws IOException { this.endpoint = endpoint; this.callback = callback; pipe = new RandomAccessFile(endpoint.getPipe(), "rw"); FileDescriptor fd = pipe.getFD(); readChannel = new FileInputStream(fd).getChannel(); writeChannel = new FileOutputStream(fd).getChannel(); if (log.isDebugEnabled()) { log.debug("Pipe " + endpoint.getPipe().getAbsolutePath() + " opened"); } } public void run() { running = true; ByteBuffer readBuffer = ByteBuffer.allocate(1024); try { while (true) { ProtocolDecoder decoder; decoder = endpoint.getProtocol().createProtocolDecoder(); while (true) { while (decoder.inputRequired()) { int c; try { c = readChannel.read(readBuffer); } catch (IOException ex) { log.error("Error while reading from pipe " + endpoint.getPipe().getAbsolutePath() + "; shutting down listener", ex); return; } if (c == -1) { log.error("Pipe " + endpoint.getPipe().getAbsolutePath() + " was unexpectedly closed; shutting down listener"); return; } synchronized (guard) { if (!running) { return; } } decoder.decode(readBuffer.array(), 0, readBuffer.position()); readBuffer.rewind(); } byte[] message = decoder.getNext(); callback.receive(endpoint, message, message.length, null); } } } finally { try { pipe.close(); if (log.isDebugEnabled()) { log.debug("Pipe " + endpoint.getPipe().getAbsolutePath() + " closed"); } } catch (IOException ex) { log.warn("Error while closing pipe " + endpoint.getPipe().getAbsolutePath(), ex); } done.countDown(); } } public void stop() throws IOException { if (log.isDebugEnabled()) { log.debug("Stopping listener for pipe " + endpoint.getPipe().getAbsolutePath() + " ..."); } synchronized (guard) { running = false; writeChannel.write(ByteBuffer.allocate(1)); } try { done.await(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } if (log.isDebugEnabled()) { log.debug("Listener for pipe " + endpoint.getPipe().getAbsolutePath() + " stopped"); } } }