001/**************************************************************** 002 * Licensed to the Apache Software Foundation (ASF) under one * 003 * or more contributor license agreements. See the NOTICE file * 004 * distributed with this work for additional information * 005 * regarding copyright ownership. The ASF licenses this file * 006 * to you under the Apache License, Version 2.0 (the * 007 * "License"); you may not use this file except in compliance * 008 * with the License. You may obtain a copy of the License at * 009 * * 010 * http://www.apache.org/licenses/LICENSE-2.0 * 011 * * 012 * Unless required by applicable law or agreed to in writing, * 013 * software distributed under the License is distributed on an * 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * 015 * KIND, either express or implied. See the License for the * 016 * specific language governing permissions and limitations * 017 * under the License. * 018 ****************************************************************/ 019 020package org.apache.james.mpt.session; 021 022import java.io.IOException; 023import java.nio.ByteBuffer; 024import java.nio.channels.SocketChannel; 025import java.nio.charset.Charset; 026import java.util.concurrent.Callable; 027import java.util.concurrent.TimeUnit; 028 029import org.apache.commons.lang.NotImplementedException; 030import org.apache.commons.lang.mutable.MutableInt; 031import org.apache.james.mpt.api.Monitor; 032import org.apache.james.mpt.api.Session; 033 034import com.jayway.awaitility.Awaitility; 035import com.jayway.awaitility.Duration; 036 037public final class ExternalSession implements Session { 038 039 private static final byte[] CRLF = { '\r', '\n' }; 040 041 private final SocketChannel socket; 042 043 private final Monitor monitor; 044 045 private final ByteBuffer readBuffer; 046 047 private final Charset ascii; 048 049 private final ByteBuffer lineEndBuffer; 050 051 private boolean first = true; 052 053 private final String shabang; 054 055 public ExternalSession(SocketChannel socket, Monitor monitor, String shabang) { 056 this(socket, monitor, shabang, false); 057 } 058 059 public ExternalSession(SocketChannel socket, Monitor monitor, String shabang, boolean debug) { 060 super(); 061 this.socket = socket; 062 this.monitor = monitor; 063 readBuffer = ByteBuffer.allocateDirect(2048); 064 ascii = Charset.forName("US-ASCII"); 065 lineEndBuffer = ByteBuffer.wrap(CRLF); 066 this.shabang = shabang; 067 } 068 069 public String readLine() throws Exception { 070 StringBuffer buffer = new StringBuffer(); 071 readlineInto(buffer); 072 final String result; 073 if (first && shabang != null) { 074 // fake shabang 075 monitor.note("<-" + buffer.toString()); 076 result = shabang; 077 first = false; 078 } 079 else { 080 result = buffer.toString(); 081 monitor.note("<-" + result); 082 } 083 return result; 084 } 085 086 private void readlineInto(StringBuffer buffer) throws Exception { 087 monitor.debug("[Reading line]"); 088 readBuffer.flip(); 089 while (oneFromLine(buffer)) 090 ; 091 // May have partial read 092 readBuffer.compact(); 093 monitor.debug("[Done]"); 094 } 095 096 private boolean oneFromLine(StringBuffer buffer) throws Exception { 097 final boolean result; 098 if (readBuffer.hasRemaining()) { 099 char next = (char) readBuffer.get(); 100 if (next == '\n') { 101 monitor.debug("[LF]"); 102 // Reached end of the line 103 result = false; 104 } 105 else if (next == '\r') { 106 // CRLF line endings so drop 107 monitor.debug("[CR]"); 108 result = true; 109 } 110 else { 111 // Load buffer 112 monitor.debug(next); 113 buffer.append(next); 114 result = true; 115 } 116 } 117 else { 118 monitor.debug("[Reading into buffer]"); 119 readBuffer.clear(); 120 result = tryReadFromSocket(); 121 // Reset for transfer into string buffer 122 readBuffer.flip(); 123 monitor.debug(String.format("[Read %d characters]", readBuffer.limit())); 124 } 125 return result; 126 } 127 128 private boolean tryReadFromSocket() throws IOException, InterruptedException { 129 final MutableInt status = new MutableInt(0); 130 Awaitility 131 .waitAtMost(Duration.ONE_MINUTE) 132 .pollDelay(new Duration(10, TimeUnit.MILLISECONDS)) 133 .until(new Callable<Boolean>() { 134 @Override 135 public Boolean call() throws Exception { 136 int read = socket.read(readBuffer); 137 status.setValue(read); 138 return read != 0; 139 } 140 }); 141 if (status.intValue() == -1) { 142 monitor.debug("Error reading, got -1"); 143 return false; 144 } 145 return true; 146 } 147 148 public void start() throws Exception { 149 while (!socket.finishConnect()) { 150 monitor.note("connecting..."); 151 Thread.sleep(10); 152 } 153 } 154 155 public void restart() throws Exception { 156 throw new NotImplementedException("Restart is not implemented for ExternalSession"); 157 } 158 159 public void stop() throws Exception { 160 monitor.note("closing"); 161 socket.close(); 162 } 163 164 public void writeLine(String line) throws Exception { 165 monitor.note("-> " + line); 166 monitor.debug("[Writing line]"); 167 ByteBuffer writeBuffer = ascii.encode(line); 168 while (writeBuffer.hasRemaining()) { 169 socket.write(writeBuffer); 170 } 171 lineEndBuffer.rewind(); 172 while (lineEndBuffer.hasRemaining()) { 173 socket.write(lineEndBuffer); 174 } 175 monitor.debug("[Done]"); 176 } 177 178 /** 179 * Constructs a <code>String</code> with all attributes in name = value 180 * format. 181 * 182 * @return a <code>String</code> representation of this object. 183 */ 184 public String toString() { 185 final String TAB = " "; 186 187 return "External ( " + "socket = " + this.socket + TAB + "monitor = " + this.monitor + TAB 188 + "readBuffer = " + this.readBuffer + TAB + "ascii = " + this.ascii + TAB + "lineEndBuffer = " 189 + this.lineEndBuffer + TAB + "first = " + this.first + TAB + "shabang = " + this.shabang + TAB + " )"; 190 } 191 192}