1
2
3
4
5
6
7
8
9
10
11
12
13 package org.codehaus.plexus.build.connect;
14
15 import javax.inject.Named;
16 import javax.inject.Singleton;
17
18 import java.io.Closeable;
19 import java.io.DataInputStream;
20 import java.io.DataOutputStream;
21 import java.io.IOException;
22 import java.net.ServerSocket;
23 import java.net.Socket;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.UUID;
28 import java.util.WeakHashMap;
29 import java.util.concurrent.ExecutorService;
30 import java.util.concurrent.Executors;
31 import java.util.concurrent.atomic.AtomicBoolean;
32 import java.util.function.BiConsumer;
33 import java.util.function.Function;
34
35 import org.apache.maven.execution.MavenSession;
36 import org.codehaus.plexus.build.connect.messages.Message;
37
38
39
40
41
42
43 @Named("default")
44 @Singleton
45 public class TcpBuildConnection implements BuildConnection {
46 private static final String PLEXUS_BUILD_IPC_PORT = "plexus.build.ipc.port";
47
48 private static final int PORT = Integer.getInteger(PLEXUS_BUILD_IPC_PORT, 0);
49
50 private final Map<MavenSession, String> sessionMap = new WeakHashMap<>();
51
52 private final ThreadLocal<TcpClientConnection> connections =
53 ThreadLocal.withInitial(() -> new TcpClientConnection());
54
55 @Override
56 public boolean isEnabled() {
57 return PORT > 0;
58 }
59
60 @Override
61 public Message send(Message message, MavenSession mavenSession) {
62 if (isEnabled()) {
63 String sessionId = getId(mavenSession);
64 byte[] messageBytes = message.serialize(sessionId);
65 byte[] replyBytes = connections.get().send(messageBytes);
66 if (replyBytes.length > 0) {
67 return Message.decode(replyBytes);
68 }
69 }
70 return null;
71 }
72
73 private synchronized String getId(MavenSession session) {
74 if (session == null) {
75 return Thread.currentThread().getName();
76 }
77 return sessionMap.computeIfAbsent(session, x -> UUID.randomUUID().toString());
78 }
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 public static ServerConnection createServer(Function<Message, Map<String, String>> consumer) throws IOException {
99 return new ServerConnection(new ServerSocket(0), consumer);
100 }
101
102
103
104
105
106 public static final class ServerConnection implements AutoCloseable {
107
108 private ServerSocket socket;
109 private ExecutorService executor = Executors.newCachedThreadPool();
110 private List<TcpServerConnection> connections = new ArrayList<>();
111
112 ServerConnection(ServerSocket socket, Function<Message, Map<String, String>> consumer) {
113 this.socket = socket;
114 executor.execute(() -> {
115 while (!Thread.currentThread().isInterrupted()) {
116 try {
117 TcpServerConnection connection = new TcpServerConnection(socket.accept(), consumer);
118 connections.add(connection);
119 executor.execute(connection);
120 } catch (IOException e) {
121 return;
122 }
123 }
124 });
125 }
126
127 @Override
128 public void close() {
129 executor.shutdownNow();
130 for (TcpServerConnection connection : connections) {
131 connection.close();
132 }
133 try {
134 socket.close();
135 } catch (IOException e) {
136 }
137 }
138
139
140
141
142
143
144 public void setupProcess(BiConsumer<String, String> consumer) {
145
146
147 consumer.accept(PLEXUS_BUILD_IPC_PORT, Integer.toString(socket.getLocalPort()));
148 }
149 }
150
151 private static final class TcpServerConnection implements Runnable, Closeable {
152
153 private Socket socket;
154 private Function<Message, Map<String, String>> consumer;
155 private DataInputStream in;
156 private DataOutputStream out;
157 private AtomicBoolean closed = new AtomicBoolean();
158
159 public TcpServerConnection(Socket socket, Function<Message, Map<String, String>> consumer) throws IOException {
160 this.socket = socket;
161 this.consumer = consumer;
162 in = new DataInputStream(socket.getInputStream());
163 out = new DataOutputStream(socket.getOutputStream());
164 }
165
166 @Override
167 public void run() {
168 try {
169 while (!closed.get() && !Thread.currentThread().isInterrupted()) {
170 try {
171 int length = in.readInt();
172 if (length == 0) {
173 return;
174 }
175 byte[] bytes = new byte[length];
176 in.readFully(bytes);
177 Message message = Message.decode(bytes);
178 Map<String, String> payload = consumer.apply(message);
179 Message reply = Message.replyTo(message, payload);
180 byte[] responseBytes = reply.serialize();
181 synchronized (out) {
182 out.writeInt(responseBytes.length);
183 out.write(responseBytes);
184 out.flush();
185 }
186 } catch (Exception e) {
187 return;
188 }
189 }
190 } finally {
191 close();
192 }
193 }
194
195 @Override
196 public void close() {
197 if (closed.compareAndSet(false, true)) {
198 try {
199 synchronized (out) {
200 out.writeInt(0);
201 out.flush();
202 }
203 } catch (IOException e) {
204 }
205 try {
206 socket.close();
207 } catch (IOException e) {
208 }
209 }
210 }
211 }
212
213 private static final class TcpClientConnection {
214
215 private Socket socket;
216 private boolean closed;
217 private DataInputStream in;
218 private DataOutputStream out;
219
220 public byte[] send(byte[] messageBytes) {
221 if (!closed) {
222 try {
223 if (socket == null) {
224 socket = new Socket("localhost", PORT);
225 in = new DataInputStream(socket.getInputStream());
226 out = new DataOutputStream(socket.getOutputStream());
227 }
228 out.writeInt(messageBytes.length);
229 out.write(messageBytes);
230 out.flush();
231 int length = in.readInt();
232 if (length == 0) {
233 socket.close();
234 closed = true;
235 } else {
236 byte[] bytes = new byte[length];
237 in.readFully(bytes);
238 return bytes;
239 }
240 } catch (IOException e) {
241 closed = true;
242 if (socket != null) {
243 try {
244 socket.close();
245 } catch (IOException e1) {
246 }
247 }
248 }
249 }
250 return new byte[0];
251 }
252 }
253 }