/* * Copyright 2014 Robert von Burg <eitch@eitchnet.ch> * * 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 li.strolch.communication.file; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.text.MessageFormat; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import li.strolch.communication.CommunicationConnection; import li.strolch.communication.CommunicationEndpoint; import li.strolch.communication.ConnectionException; import li.strolch.communication.ConnectionMessages; import li.strolch.communication.ConnectionState; import li.strolch.communication.IoMessage; import li.strolch.communication.IoMessageVisitor; import li.strolch.communication.StreamMessageVisitor; import li.strolch.utils.helper.StringHelper; /** * An {@link CommunicationEndpoint} which writes and/or reads from a designated file * * @author Robert von Burg <eitch@eitchnet.ch> */ public class FileEndpoint implements CommunicationEndpoint, Runnable { public static final String ENDPOINT_MODE = "endpointMode"; //$NON-NLS-1$ public static final String INBOUND_FILENAME = "inboundFilename"; //$NON-NLS-1$ public static final String OUTBOUND_FILENAME = "outboundFilename"; //$NON-NLS-1$ public static final long POLL_TIME = 1000l; private static final Logger logger = LoggerFactory.getLogger(FileEndpoint.class); private CommunicationConnection connection; private FileEndpointMode endpointMode; private String inboundFilename; private String outboundFilename; private Thread thread; private boolean run = false; private StreamMessageVisitor messageVisitor; /** * {@link FileEndpoint} needs the following parameters on the configuration to be initialized * <ul> * <li>outboundFilename: the file name where the {@link IoMessage} contents are written to. The value may contain * {@link System#getProperty(String)} place holders which will be evaluated</li> * </ul> */ @Override public void configure(CommunicationConnection connection, IoMessageVisitor messageVisitor) { this.connection = connection; ConnectionMessages.assertLegalMessageVisitor(this.getClass(), StreamMessageVisitor.class, messageVisitor); this.messageVisitor = (StreamMessageVisitor) messageVisitor; configure(); } private void configure() { Map<String, String> parameters = this.connection.getParameters(); String endpointModeS = parameters.get(ENDPOINT_MODE); if (StringHelper.isEmpty(endpointModeS)) { throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, ENDPOINT_MODE, endpointModeS); } try { this.endpointMode = FileEndpointMode.valueOf(endpointModeS); } catch (Exception e) { throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, ENDPOINT_MODE, endpointModeS); } if (this.endpointMode.isRead()) { this.inboundFilename = parameters.get(INBOUND_FILENAME); if (StringHelper.isEmpty(this.inboundFilename)) { throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, INBOUND_FILENAME, this.inboundFilename); } } if (this.endpointMode.isWrite()) { this.outboundFilename = parameters.get(OUTBOUND_FILENAME); if (StringHelper.isEmpty(this.outboundFilename)) { throw ConnectionMessages.throwInvalidParameter(FileEndpoint.class, OUTBOUND_FILENAME, this.outboundFilename); } } } @Override public String getLocalUri() { return new File(this.inboundFilename).getAbsolutePath(); } @Override public String getRemoteUri() { return new File(this.outboundFilename).getAbsolutePath(); } @Override public void start() { if (this.endpointMode.isRead()) { this.thread = new Thread(this, new File(this.inboundFilename).getName()); this.run = true; this.thread.start(); } this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); } @Override public void stop() { stopThread(); this.connection.notifyStateChange(ConnectionState.DISCONNECTED, ConnectionState.DISCONNECTED.toString()); } @Override public void reset() { stopThread(); configure(); this.connection.notifyStateChange(ConnectionState.INITIALIZED, ConnectionState.INITIALIZED.toString()); } private void stopThread() { this.run = false; if (this.thread != null) { try { this.thread.interrupt(); this.thread.join(2000l); } catch (Exception e) { logger.error(MessageFormat.format("Error while interrupting thread: {0}", e.getLocalizedMessage())); //$NON-NLS-1$ } this.thread = null; } } @Override public void send(IoMessage message) throws Exception { if (!this.endpointMode.isWrite()) { String msg = "FileEnpoint mode is {0} and thus write is not allowed!"; //$NON-NLS-1$ msg = MessageFormat.format(msg, this.endpointMode); throw new ConnectionException(msg); } this.connection.notifyStateChange(ConnectionState.WORKING, ConnectionState.WORKING.toString()); // open the stream try (FileOutputStream outputStream = new FileOutputStream(this.outboundFilename, false)) { // write the message using the visitor this.messageVisitor.visit(outputStream, message); } finally { this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); } } @Override public void simulate(IoMessage message) throws Exception { this.messageVisitor.simulate(message); } @Override public void run() { File file = new File(this.inboundFilename); long lastModified = 0l; logger.info("Starting..."); //$NON-NLS-1$ while (this.run) { try { if (file.canRead()) { long tmpModified = file.lastModified(); if (tmpModified > lastModified) { logger.info(MessageFormat.format("Handling file {0}", file.getAbsolutePath())); //$NON-NLS-1$ this.connection.notifyStateChange(ConnectionState.WORKING, ConnectionState.WORKING.toString()); // file is changed lastModified = tmpModified; // read the file handleFile(file); this.connection.notifyStateChange(ConnectionState.IDLE, ConnectionState.IDLE.toString()); } } if (this.run) { this.connection.notifyStateChange(ConnectionState.WAITING, ConnectionState.WAITING.toString()); try { synchronized (this) { this.wait(POLL_TIME); } } catch (InterruptedException e) { this.run = false; logger.info("Interrupted!"); //$NON-NLS-1$ } } } catch (Exception e) { logger.error(MessageFormat.format("Error reading file: {0}", file.getAbsolutePath())); //$NON-NLS-1$ logger.error(e.getMessage(), e); this.connection.notifyStateChange(ConnectionState.BROKEN, e.getLocalizedMessage()); } } } /** * Reads the file and handle using {@link StreamMessageVisitor} * * @param file * the {@link File} to read */ protected void handleFile(File file) throws Exception { try (InputStream inputStream = new FileInputStream(file)) { // convert the object to an integration message IoMessage message = this.messageVisitor.visit(inputStream); // and forward to the connection if (message != null) { this.connection.handleNewMessage(message); } } } }