1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 package org.openoffice.test.vcl.client;
25 
26 import java.io.DataInputStream;
27 import java.io.DataOutputStream;
28 import java.io.IOException;
29 import java.net.InetSocketAddress;
30 import java.net.Socket;
31 import java.util.List;
32 import java.util.Vector;
33 import java.util.logging.Level;
34 import java.util.logging.Logger;
35 
36 import org.openoffice.test.common.SystemUtil;
37 
38 /**
39  * Manage the communication with the automation server.
40  * It's used to establish the connection, send and receive data package.
41  * Data package format:
42  *
43  * | [Force Multi Channel (0xFFFFFFFF)] | Data Length (32 bits) | Check Byte (8 bits) | Header Length (16 bits) | Header Data | Body Data |
44  *
45  * To handle the received data, add a communication listener to the manager.
46  * The listner will be called back when data package arrives.
47  *
48  */
49 public class CommunicationManager implements Runnable, Constant{
50 
51 	private static Logger logger = Logger.getLogger("CommunicationManager");
52 
53 	private final static int DEFAULT_PORT = 12479;
54 
55 	private String host = "localhost";
56 
57 	private int port = DEFAULT_PORT;
58 
59 	private Socket socket = null;
60 
61 	private double reconnectInterval = 2;
62 
63 	private int reconnectCount = 5;
64 
65 	private List<CommunicationListener> listeners = new Vector<CommunicationListener>();
66 
67 	/**
68 	 * Create a communication manager with the default host and port.
69 	 * The default host is local and the default port is 12479.
70 	 *
71 	 */
CommunicationManager()72 	public CommunicationManager() {
73 		try {
74 			String portValue = System.getProperty("openoffice.automation.port");
75 			if (portValue != null)
76 				port = Integer.parseInt(portValue);
77 		} catch (NumberFormatException e) {
78 			// use default
79 		}
80 	}
81 
82 	/**
83 	 * Create a communication manager with the given host and port
84 	 * @param host
85 	 * @param port
86 	 */
CommunicationManager(String host, int port)87 	public CommunicationManager(String host, int port) {
88 		this.host = host;
89 		this.port = port;
90 	}
91 
92 	/**
93 	 * Send a data package to server
94 	 * @param headerType the package header type
95 	 * @param header the data in the header
96 	 * @param data the data in the body
97 	 */
sendPackage(int headerType, byte[] header, byte[] data)98 	public synchronized void sendPackage(int headerType, byte[] header, byte[] data) throws CommunicationException {
99 		if (socket == null)
100 			start();
101 
102 		try {
103 			if (header == null)
104 				header = new byte[0];
105 
106 			if (data == null)
107 				data = new byte[0];
108 
109 			DataOutputStream os = new DataOutputStream(socket.getOutputStream());
110 			int len = 1 + 2 + 2 + header.length + data.length;
111 			// Total len
112 			os.writeInt(len);
113 			// Check byte
114 			os.writeByte(calcCheckByte(len));
115 			// Header len
116 			os.writeChar(2 + header.length);
117 			// Header
118 			os.writeChar(headerType);
119 			os.write(header);
120 			// Data
121 			os.write(data);
122 			os.flush();
123 		} catch (IOException e) {
124 			stop();
125 			throw new CommunicationException("Failed to send data to automation server!", e);
126 		}
127 	}
128 
129 	/**
130 	 * Start a new thread to read the data sent by sever
131 	 */
run()132 	public void run() {
133 		try {
134 			while (socket != null) {
135 				DataInputStream is = new DataInputStream(socket.getInputStream());
136 
137 				int len = is.readInt();
138 				if (len == 0xFFFFFFFF)
139 					len = is.readInt();
140 
141 				byte checkByte = is.readByte();
142 				if (calcCheckByte(len) != checkByte)
143 					throw new CommunicationException("Bad data package. Wrong check byte.");
144 
145 				int headerLen = is.readUnsignedShort();
146 				int headerType = is.readUnsignedShort();
147 				byte[] header = new byte[headerLen - 2];
148 				is.readFully(header);
149 				byte[] data = new byte[len - headerLen - 3];
150 				is.readFully(data);
151 				for (int i = 0; i < listeners.size(); i++)
152 					((CommunicationListener) listeners.get(i)).received(headerType, header, data);
153 			}
154 		} catch (Exception e) {
155 			logger.log(Level.FINEST, "Failed to receive data!", e);
156 			stop();
157 		}
158 	}
159 
160 	/**
161 	 * Add a communication listener
162 	 * @param listener
163 	 */
addListener(CommunicationListener listener)164 	public void addListener(CommunicationListener listener) {
165 		if (listener != null && !listeners.contains(listener))
166 			listeners.add(listener);
167 	}
168 
169 
170 	/**
171 	 * Stop the communication manager.
172 	 *
173 	 */
stop()174 	public synchronized void stop() {
175 		if (socket == null)
176 			return;
177 
178 		try {
179 			socket.close();
180 		} catch (IOException e) {
181 			//ignore
182 		}
183 		socket = null;
184 		logger.log(Level.CONFIG, "Stop Communication Manager");
185 		for (int i = 0; i < listeners.size(); i++)
186 			((CommunicationListener) listeners.get(i)).stop();
187 	}
188 
isConnected()189 	public synchronized boolean isConnected() {
190 		return socket != null;
191 	}
192 
193 
connect()194 	public synchronized void connect() throws IOException {
195 		if (socket != null)
196 			return;
197 
198 		try{
199 			socket = new Socket();
200 			socket.setTcpNoDelay(true);
201 			socket.setSoTimeout(240 * 1000); // if in 4 minutes we get nothing from server, an exception will thrown.
202 			socket.connect(new InetSocketAddress(host, port));
203 			Thread thread = new Thread(this);
204 			thread.setDaemon(true);
205 			thread.start();
206 		} catch (IOException e){
207 			socket = null;
208 			throw e;
209 		}
210 	}
211 
212 	/**
213 	 * Start the communication manager.
214 	 *
215 	 */
start()216 	public synchronized void start() {
217 		logger.log(Level.CONFIG, "Start Communication Manager");
218 		//connect and retry if fails
219 		for (int i = 0; i < reconnectCount; i++) {
220 			try {
221 				connect();
222 				return;
223 			} catch (IOException e) {
224 				logger.log(Level.FINEST, "Failed to connect! Tried " + i, e);
225 			}
226 
227 			SystemUtil.sleep(reconnectInterval);
228 		}
229 
230 		throw new CommunicationException("Failed to connect to automation server on: " + host + ":" + port);
231 	}
232 
233 
calcCheckByte(int i)234 	private static byte calcCheckByte(int i) {
235 		int nRes = 0;
236 		int[] bytes = new int[4];
237 		bytes[0] = (i >>> 24) & 0x00FF;
238 		bytes[1] = (i >>> 16) & 0x00FF;
239 		bytes[2] = (i >>> 8) & 0x00FF;
240 		bytes[3] = (i >>> 0) & 0x00FF;
241 		nRes += bytes[0] ^ 0xf0;
242 		nRes += bytes[1] ^ 0x0f;
243 		nRes += bytes[2] ^ 0xf0;
244 		nRes += bytes[3] ^ 0x0f;
245 		nRes ^= (nRes >>> 8);
246 		return (byte) nRes;
247 	}
248 }
249