├── .gitignore ├── src ├── main │ └── java │ │ └── org │ │ └── terracotta │ │ └── ipceventbus │ │ ├── event │ │ ├── ErrorListener.java │ │ ├── EventListenerAdapter.java │ │ ├── ErrorListenerAdapter.java │ │ ├── EventBusException.java │ │ ├── RemoteEventBus.java │ │ ├── EventBusIOException.java │ │ ├── EventListener.java │ │ ├── EventBusListenerException.java │ │ ├── RethrowingErrorListener.java │ │ ├── EventSender.java │ │ ├── Assert.java │ │ ├── PrintingErrorListener.java │ │ ├── Event.java │ │ ├── EventListenerSniffer.java │ │ ├── EventBus.java │ │ ├── Listeners.java │ │ ├── EventBusClient.java │ │ ├── EventBusServer.java │ │ ├── BaseBuilder.java │ │ ├── DefaultEvent.java │ │ ├── DefaultEventBus.java │ │ ├── DefaultEventBusClient.java │ │ └── DefaultEventBusServer.java │ │ ├── proc │ │ ├── Jna.java │ │ ├── Bus.java │ │ ├── EventJavaProcessBuilder.java │ │ ├── Boot.java │ │ ├── JavaProcess.java │ │ ├── EventJavaProcess.java │ │ ├── AnyProcessBuilder.java │ │ ├── JavaProcessBuilder.java │ │ └── AnyProcess.java │ │ ├── ThreadUtil.java │ │ └── io │ │ ├── Pipe.java │ │ └── MultiplexOutputStream.java └── test │ └── java │ └── org │ └── terracotta │ └── ipceventbus │ ├── proc │ ├── Echo.java │ ├── EchoFail.java │ ├── EchoEvent2.java │ ├── draft │ │ ├── SocketClient.java │ │ ├── EchoEventCli.java │ │ ├── SocketServer.java │ │ ├── SHook.java │ │ └── Doc.java │ ├── EchoEvent.java │ ├── JavaProcessTest.java │ ├── EventJavaProcessTest.java │ └── AnyProcessTest.java │ ├── event │ ├── LocalEventBusTest.java │ ├── RecordingErrorListener.java │ ├── RecordingEventListener.java │ ├── RemoteEventBusTest.java │ ├── RemoteEventBusPeerCommunicationTest.java │ └── AbstractEventBusTest.java │ └── io │ ├── MultiplexTest.java │ └── PipeTest.java ├── azure-pipelines.yml ├── pom.xml ├── LICENSE └── README.adoc /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | target 4 | logs-* 5 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/ErrorListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.terracotta.ipceventbus.event; 18 | 19 | /** 20 | * @author Mathieu Carbou 21 | */ 22 | public interface ErrorListener { 23 | void onError(Event event, EventListener listener, Throwable e); 24 | } 25 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright Terracotta, Inc. 3 | # Copyright IBM Corp. 2024, 2025 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # See shared code location for steps and parameters: 19 | # https://dev.azure.com/TerracottaCI/_git/terracotta 20 | 21 | resources: 22 | repositories: 23 | - repository: templates 24 | type: git 25 | name: terracotta/terracotta 26 | 27 | jobs: 28 | - template: build-templates/maven-common.yml@templates 29 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/EventListenerAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | /** 21 | * @author Mathieu Carbou 22 | */ 23 | public class EventListenerAdapter implements EventListener { 24 | @Override 25 | public void onEvent(Event e) throws Throwable { 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/ErrorListenerAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | /** 21 | * @author Mathieu Carbou 22 | */ 23 | public class ErrorListenerAdapter implements ErrorListener { 24 | @Override 25 | public void onError(Event event, EventListener listener, Throwable e) { 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/EventBusException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | /** 21 | * @author Mathieu Carbou 22 | */ 23 | public class EventBusException extends RuntimeException { 24 | public EventBusException(String message) { 25 | super(message); 26 | } 27 | 28 | public EventBusException(String message, Throwable cause) { 29 | super(message, cause); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/RemoteEventBus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.terracotta.ipceventbus.event; 18 | 19 | import java.io.Closeable; 20 | 21 | /** 22 | * @author Mathieu Carbou 23 | */ 24 | public interface RemoteEventBus extends EventBus, Closeable { 25 | 26 | /** 27 | * @return The listen port 28 | */ 29 | int getServerPort(); 30 | 31 | String getServerHost(); 32 | 33 | boolean isClosed(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/EventBusIOException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | /** 21 | * @author Mathieu Carbou 22 | */ 23 | public class EventBusIOException extends EventBusException { 24 | public EventBusIOException(String message) { 25 | super(message); 26 | } 27 | 28 | public EventBusIOException(String message, Throwable cause) { 29 | super(message, cause); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/EventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.terracotta.ipceventbus.event; 18 | 19 | /** 20 | * @author Mathieu Carbou 21 | */ 22 | public interface EventListener { 23 | 24 | /** 25 | * Listen for an event 26 | * 27 | * @param e The {@link Event} object 28 | * @throws Throwable a listener can fail, and failure are handled by a {@link ErrorListener} 29 | */ 30 | void onEvent(Event e) throws Throwable; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/EventBusListenerException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | /** 21 | * @author Mathieu Carbou 22 | */ 23 | public class EventBusListenerException extends EventBusException { 24 | public EventBusListenerException(String message) { 25 | super(message); 26 | } 27 | 28 | public EventBusListenerException(String message, Throwable cause) { 29 | super(message, cause); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/RethrowingErrorListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | /** 21 | * @author Mathieu Carbou 22 | */ 23 | public class RethrowingErrorListener implements ErrorListener { 24 | @Override 25 | public void onError(Event event, EventListener listener, Throwable e) { 26 | if (e instanceof Error) throw (Error) e; 27 | if (e instanceof RuntimeException) throw (RuntimeException) e; 28 | throw new EventBusListenerException(e.getMessage(), e); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/proc/Echo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import static org.junit.Assert.assertEquals; 21 | 22 | /** 23 | * @author Mathieu Carbou 24 | */ 25 | public class Echo { 26 | public static void main(String[] args) { 27 | System.out.println(System.getenv("VAR")); 28 | System.out.println(System.getProperty("my.prop")); 29 | for (String arg : args) { 30 | System.out.println(arg); 31 | } 32 | assertEquals("Hello", System.getenv("VAR")); 33 | assertEquals("world", System.getProperty("my.prop")); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/event/LocalEventBusTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | import org.junit.Before; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.JUnit4; 23 | 24 | /** 25 | * @author Mathieu Carbou 26 | */ 27 | @RunWith(JUnit4.class) 28 | public class LocalEventBusTest extends AbstractEventBusTest { 29 | 30 | @Before 31 | public void init() { 32 | eventBus = new EventBus.Builder() 33 | .onError(errorListener) 34 | .build(); 35 | eventBus.on(new EventListenerSniffer("eventBus")); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/proc/EchoFail.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import org.terracotta.ipceventbus.ThreadUtil; 21 | 22 | import static org.junit.Assert.fail; 23 | 24 | /** 25 | * @author Mathieu Carbou 26 | */ 27 | public class EchoFail { 28 | public static void main(String[] args) throws InterruptedException { 29 | System.out.println(System.getenv("VAR")); 30 | System.out.println(System.getProperty("my.prop")); 31 | for (String arg : args) { 32 | System.out.println(arg); 33 | } 34 | ThreadUtil.minimumSleep(1000); 35 | fail("message"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/EventSender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.terracotta.ipceventbus.event; 18 | 19 | /** 20 | * @author Mathieu Carbou 21 | */ 22 | public interface EventSender { 23 | 24 | /** 25 | * @return the event bus ID, used in {@link DefaultEvent#getSource()} 26 | */ 27 | String getId(); 28 | 29 | /** 30 | * Trigger an event 31 | * 32 | * @param name the event name 33 | */ 34 | void trigger(String name); 35 | 36 | /** 37 | * Trigger an event with some data 38 | * 39 | * @param name the event name 40 | * @param data data 41 | */ 42 | void trigger(String name, Object data); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/proc/EchoEvent2.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import org.terracotta.ipceventbus.ThreadUtil; 21 | import org.terracotta.ipceventbus.event.Event; 22 | import org.terracotta.ipceventbus.event.EventListener; 23 | 24 | /** 25 | * @author Mathieu Carbou 26 | */ 27 | public class EchoEvent2 { 28 | public static void main(String[] args) throws Exception { 29 | Bus.get().on("ping", new EventListener() { 30 | @Override 31 | public void onEvent(Event e) { 32 | Bus.get().trigger("pong", e.getData()); 33 | } 34 | }); 35 | ThreadUtil.minimumSleep(2000); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/Assert.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.terracotta.ipceventbus.event; 18 | 19 | /** 20 | * @author Mathieu Carbou 21 | */ 22 | final class Assert { 23 | 24 | static void legalEventName(String name) { 25 | if (name == null) throw new NullPointerException("Event name is null"); 26 | if (name.length() == 0) throw new IllegalArgumentException(name); 27 | } 28 | 29 | static void notInternalName(String name) { 30 | if (name.startsWith("eventbus.")) throw new IllegalArgumentException(name); 31 | } 32 | 33 | static void opened(RemoteEventBus eventBus) { 34 | if (eventBus.isClosed()) throw new IllegalStateException(eventBus + " is closed."); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/PrintingErrorListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | import java.io.PrintStream; 21 | 22 | /** 23 | * @author Mathieu Carbou 24 | */ 25 | public class PrintingErrorListener implements ErrorListener { 26 | 27 | private final PrintStream out; 28 | 29 | public PrintingErrorListener() { 30 | this(System.err); 31 | } 32 | 33 | public PrintingErrorListener(PrintStream out) { 34 | this.out = out; 35 | } 36 | 37 | @Override 38 | public void onError(Event event, EventListener listener, Throwable e) { 39 | out.println("Error in listener " + listener + " for event " + event + ": " + e.getMessage()); 40 | e.printStackTrace(out); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/proc/draft/SocketClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc.draft; 19 | 20 | import javax.net.SocketFactory; 21 | import java.io.ObjectInputStream; 22 | import java.io.ObjectOutputStream; 23 | import java.net.InetSocketAddress; 24 | import java.net.Socket; 25 | 26 | /** 27 | * @author Mathieu Carbou 28 | */ 29 | public class SocketClient { 30 | public static void main(String... args) throws Exception { 31 | Socket socket = SocketFactory.getDefault().createSocket(); 32 | socket.connect(new InetSocketAddress("localhost", Integer.parseInt(args[0]))); 33 | new ObjectOutputStream(socket.getOutputStream()).writeObject("hello world!"); 34 | System.out.println("client read from server: " + new ObjectInputStream(socket.getInputStream()).readObject()); 35 | socket.close(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/event/RecordingErrorListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * @author Mathieu Carbou 25 | */ 26 | class RecordingErrorListener implements ErrorListener { 27 | 28 | int errors; 29 | List exceptions = new ArrayList(); 30 | List events = new ArrayList(); 31 | 32 | @Override 33 | public synchronized void onError(Event event, EventListener listener, Throwable e) { 34 | errors++; 35 | exceptions.add(e); 36 | events.add(event); 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | final StringBuilder sb = new StringBuilder("RecordingErrorListener{"); 42 | sb.append("errors=").append(errors); 43 | sb.append(", exceptions=").append(exceptions); 44 | sb.append(", events=").append(events); 45 | sb.append('}'); 46 | return sb.toString(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/Event.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.terracotta.ipceventbus.event; 18 | 19 | /** 20 | * @author Mathieu Carbou 21 | */ 22 | public interface Event { 23 | 24 | boolean isUserEvent(); 25 | 26 | /** 27 | * @return The ID of the {@link EventBus} where the event comes from. Same as {@link EventBus#getId()} 28 | */ 29 | String getSource(); 30 | 31 | /** 32 | * @return The event name triggered 33 | */ 34 | String getName(); 35 | 36 | /** 37 | * @return The millisecond timestamp of this event 38 | */ 39 | long getTimestamp(); 40 | 41 | /** 42 | * @return The raw data of this event, if any 43 | */ 44 | Object getData(); 45 | 46 | /** 47 | * Convert the raw data in given type 48 | * 49 | * @param type The class of wanted type 50 | * @param The type to cast the raw data into 51 | * @return The casted raw data 52 | */ 53 | T getData(Class type); 54 | 55 | T getData(Class type, T defaultValue); 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/proc/Jna.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import com.sun.jna.Pointer; 21 | import com.sun.jna.platform.win32.Kernel32; 22 | import com.sun.jna.platform.win32.WinNT; 23 | 24 | import java.lang.reflect.Field; 25 | 26 | /** 27 | * @author Mathieu Carbou 28 | */ 29 | class Jna { 30 | 31 | static long getWindowsPid(Process process) { 32 | if (process.getClass().getName().equals("java.lang.Win32Process") || process.getClass().getName().equals("java.lang.ProcessImpl")) { 33 | try { 34 | Field f = process.getClass().getDeclaredField("handle"); 35 | f.setAccessible(true); 36 | long handl = f.getLong(process); 37 | Kernel32 kernel = Kernel32.INSTANCE; 38 | WinNT.HANDLE handle = new WinNT.HANDLE(); 39 | handle.setPointer(Pointer.createConstant(handl)); 40 | return kernel.GetProcessId(handle); 41 | } catch (Throwable ignored) { 42 | } 43 | } 44 | return -1; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/proc/draft/EchoEventCli.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc.draft; 19 | 20 | import org.terracotta.ipceventbus.event.EventBusClient; 21 | import org.terracotta.ipceventbus.event.EventListenerSniffer; 22 | 23 | import java.io.IOException; 24 | import java.lang.management.ManagementFactory; 25 | 26 | /** 27 | * @author Mathieu Carbou 28 | */ 29 | public class EchoEventCli { 30 | public static void main(String[] args) throws InterruptedException, IOException { 31 | System.out.println(getCurrentPid()); 32 | EventBusClient eb = new EventBusClient.Builder() 33 | .id(getCurrentPid()) 34 | .connect(49475) 35 | .build(); 36 | eb.on(new EventListenerSniffer()); 37 | eb.trigger("echo", "Hello world!"); 38 | eb.trigger("echo", "Hello you!"); 39 | eb.trigger("exit"); 40 | } 41 | 42 | private static String getCurrentPid() { 43 | try { 44 | return ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; 45 | } catch (Exception ignored) { 46 | return null; 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/proc/draft/SocketServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc.draft; 19 | 20 | import javax.net.ServerSocketFactory; 21 | import java.io.ObjectInputStream; 22 | import java.io.ObjectOutputStream; 23 | import java.net.InetSocketAddress; 24 | import java.net.ServerSocket; 25 | import java.net.Socket; 26 | 27 | /** 28 | * @author Mathieu Carbou 29 | */ 30 | public class SocketServer { 31 | public static void main(String... args) throws Exception { 32 | 33 | ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(); 34 | serverSocket.bind(new InetSocketAddress("0.0.0.0", Integer.parseInt(args[0]))); 35 | Socket socket = serverSocket.accept(); 36 | System.out.println("server read from client: " + new ObjectInputStream(socket.getInputStream()).readObject()); 37 | ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream()); 38 | objectOutputStream.writeObject("hello world!"); 39 | objectOutputStream.flush(); 40 | socket.close(); 41 | serverSocket.close(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/EventListenerSniffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | import java.io.PrintStream; 21 | 22 | /** 23 | * @author Mathieu Carbou 24 | */ 25 | public class EventListenerSniffer implements EventListener { 26 | 27 | private final PrintStream out; 28 | private final String category; 29 | 30 | public EventListenerSniffer() { 31 | this("", System.out); 32 | } 33 | 34 | public EventListenerSniffer(PrintStream out) { 35 | this("", out); 36 | } 37 | 38 | public EventListenerSniffer(String category) { 39 | this(category, System.out); 40 | } 41 | 42 | public EventListenerSniffer(String category, PrintStream out) { 43 | this.out = out; 44 | this.category = category.length() > 0 ? (" [" + category + "]") : category; 45 | } 46 | 47 | @Override 48 | public void onEvent(Event e) throws Throwable { 49 | out.println(System.currentTimeMillis() + category + " [" + Thread.currentThread().getName() + "] " + e.getName() + "@" + e.getSource() + " at " + e.getTimestamp() + (e.getData() == null ? "" : (" - " + e.getData()))); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/ThreadUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus; 19 | 20 | import java.util.concurrent.TimeUnit; 21 | 22 | // Java does not provide a guarantee that Thread.sleep will actually sleep long enough. 23 | // In fact, on Windows, it does not sleep for long enough. 24 | // This method keeps sleeping until the full time has passed. 25 | // 26 | // Using System.nanoTime (accurate to 1 micro-second or better) in lieu of System.currentTimeMillis (on Windows 27 | // accurate to ~16ms), the inaccuracy of which compounds when invoked multiple times, as in this method. 28 | 29 | public class ThreadUtil { 30 | public static void minimumSleep(long millis) throws InterruptedException { 31 | long nanos = TimeUnit.MILLISECONDS.toNanos(millis); 32 | long start = System.nanoTime(); 33 | 34 | while (true) { 35 | long nanosLeft = nanos - (System.nanoTime() - start); 36 | 37 | if (nanosLeft <= 0) { 38 | break; 39 | } 40 | 41 | try { 42 | TimeUnit.NANOSECONDS.sleep(nanosLeft); 43 | } catch (InterruptedException e) { 44 | Thread.currentThread().interrupt(); 45 | throw e; 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/event/RecordingEventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * @author Mathieu Carbou 25 | */ 26 | class RecordingEventListener implements EventListener { 27 | 28 | volatile int events; 29 | volatile int userEvents; 30 | volatile int systemEvents; 31 | List sources = new ArrayList(); 32 | List names = new ArrayList(); 33 | 34 | @Override 35 | public synchronized void onEvent(Event e) throws Throwable { 36 | events++; 37 | if (e.isUserEvent()) userEvents++; 38 | else systemEvents++; 39 | names.add(e.getName()); 40 | sources.add(e.getSource()); 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | final StringBuilder sb = new StringBuilder("RecordingEventListener{"); 46 | sb.append("events=").append(events); 47 | sb.append(", userEvents=").append(userEvents); 48 | sb.append(", systemEvents=").append(systemEvents); 49 | sb.append(", sources=").append(sources); 50 | sb.append(", names=").append(names); 51 | sb.append('}'); 52 | return sb.toString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/EventBus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.terracotta.ipceventbus.event; 18 | 19 | /** 20 | * @author Mathieu Carbou 21 | */ 22 | public interface EventBus extends EventSender { 23 | 24 | /** 25 | * Register a new listener for an event 26 | * 27 | * @param event The event name 28 | * @param listener The listener to register 29 | */ 30 | void on(String event, EventListener listener); 31 | 32 | /** 33 | * Register a new listener for all event 34 | * 35 | * @param listener The listener to register 36 | */ 37 | void on(EventListener listener); 38 | 39 | /** 40 | * Unbind all listeners of an event 41 | * 42 | * @param event the event name 43 | */ 44 | void unbind(String event); 45 | 46 | /** 47 | * unbind this listener from all events 48 | * 49 | * @param listener the listener 50 | */ 51 | void unbind(EventListener listener); 52 | 53 | /** 54 | * unbind a listener from a specific event 55 | * 56 | * @param event the event name 57 | * @param listener the listener 58 | */ 59 | void unbind(String event, EventListener listener); 60 | 61 | final class Builder extends BaseBuilder { 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/proc/draft/SHook.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc.draft; 19 | 20 | import java.io.IOException; 21 | import java.io.OutputStream; 22 | import java.net.Socket; 23 | 24 | /** 25 | * @author Mathieu Carbou 26 | */ 27 | public class SHook { 28 | public static void main(String[] args) throws IOException { 29 | Socket socket = new Socket("localhost", 1234); 30 | final OutputStream outputStream = socket.getOutputStream(); 31 | 32 | /*Runtime.getRuntime().addShutdownHook(new Thread() { 33 | @Override 34 | public void run() { 35 | // run nc -l 1234 36 | try { 37 | Socket socket = new Socket("localhost", 1234); 38 | socket.getOutputStream().write("bye\n".getBytes()); 39 | socket.flush(); 40 | } catch (IOException uglyBigCatch) { 41 | uglyBigCatch.printStackTrace(); 42 | } 43 | } 44 | });*/ 45 | 46 | Runtime.getRuntime().addShutdownHook(new Thread() { 47 | @Override 48 | public void run() { 49 | try { 50 | outputStream.write("bye\n".getBytes()); 51 | outputStream.flush(); 52 | } catch (IOException e) { 53 | e.printStackTrace(); 54 | } 55 | } 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/proc/Bus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import org.terracotta.ipceventbus.event.EventBusClient; 21 | import org.terracotta.ipceventbus.event.EventListenerSniffer; 22 | 23 | import java.lang.management.ManagementFactory; 24 | 25 | /** 26 | * @author Mathieu Carbou 27 | */ 28 | public final class Bus { 29 | 30 | private static final EventBusClient bus; 31 | 32 | static { 33 | String host = System.getProperty("ipc.bus.host"); 34 | int port = Integer.parseInt(System.getProperty("ipc.bus.port")); 35 | String pid = getCurrentPid(); 36 | 37 | if (isDebug()) { 38 | System.out.println("[" + Boot.class.getSimpleName() + "] Child PID: " + pid); 39 | System.out.println("[" + Boot.class.getSimpleName() + "] Connecting EventBus Client " + pid + " to " + host + ":" + port + "..."); 40 | } 41 | bus = new EventBusClient.Builder() 42 | .connect(host, port) 43 | .id(pid) 44 | .build(); 45 | if (isDebug()) { 46 | bus.on(new EventListenerSniffer(pid)); 47 | } 48 | } 49 | 50 | public static EventBusClient get() { 51 | return bus; 52 | } 53 | 54 | public static boolean isDebug() { 55 | return System.getProperty("ipc.bus.debug") != null; 56 | } 57 | 58 | static String getCurrentPid() { 59 | try { 60 | return ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; 61 | } catch (Exception ignored) { 62 | return null; 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/proc/EchoEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import org.terracotta.ipceventbus.ThreadUtil; 21 | import org.terracotta.ipceventbus.event.Event; 22 | import org.terracotta.ipceventbus.event.EventBus; 23 | import org.terracotta.ipceventbus.event.EventListener; 24 | 25 | import java.lang.management.ManagementFactory; 26 | import java.util.Arrays; 27 | 28 | /** 29 | * @author Mathieu Carbou 30 | */ 31 | public class EchoEvent { 32 | public static void main(String[] args) throws Exception { 33 | // print pid 34 | System.out.println("EchoEvent: pid=" + getCurrentPid()); 35 | 36 | // print args 37 | System.out.println("EchoEvent: args=" + Arrays.asList(args)); 38 | 39 | final EventBus bus = Bus.get(); 40 | 41 | // listen to all events, even system events 42 | bus.on(new EventListener() { 43 | @Override 44 | public void onEvent(Event e) { 45 | System.out.println("EchoEvent: " + e); 46 | } 47 | }); 48 | 49 | // ping-pong 50 | bus.on("ping", new EventListener() { 51 | @Override 52 | public void onEvent(Event e) { 53 | bus.trigger("pong", e.getData()); 54 | } 55 | }); 56 | 57 | ThreadUtil.minimumSleep(1000); 58 | } 59 | 60 | private static String getCurrentPid() { 61 | try { 62 | return ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; 63 | } catch (Exception ignored) { 64 | return null; 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/Listeners.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | import java.util.Collection; 21 | import java.util.Map; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | import java.util.concurrent.ConcurrentMap; 24 | import java.util.concurrent.CopyOnWriteArrayList; 25 | import java.util.stream.Collectors; 26 | 27 | /** 28 | * @author Mathieu Carbou 29 | */ 30 | final class Listeners { 31 | 32 | private final ConcurrentMap> index; 33 | 34 | Listeners(Listeners original) { 35 | this.index = new ConcurrentHashMap<>(original.index.entrySet().stream().collect(Collectors.toMap( 36 | Map.Entry::getKey, 37 | e -> new CopyOnWriteArrayList<>(e.getValue())))); 38 | } 39 | 40 | Listeners() { 41 | this.index = new ConcurrentHashMap<>(); 42 | } 43 | 44 | public Collection on(String event) { 45 | this.index.putIfAbsent(event, new CopyOnWriteArrayList()); 46 | return this.index.get(event); 47 | } 48 | 49 | public void removeAll(String event) { 50 | index.remove(event); 51 | } 52 | 53 | public void removeAll(EventListener listener) { 54 | for (Collection listeners : index.values()) { 55 | listeners.remove(listener); 56 | } 57 | } 58 | 59 | public void remove(String event, EventListener listener) { 60 | Collection listeners = index.get(event); 61 | if (listeners != null) { 62 | listeners.remove(listener); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/EventBusClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.terracotta.ipceventbus.event; 18 | 19 | import javax.net.SocketFactory; 20 | import java.io.IOException; 21 | import java.net.InetSocketAddress; 22 | import java.net.Socket; 23 | 24 | /** 25 | * @author Mathieu Carbou 26 | */ 27 | public interface EventBusClient extends RemoteEventBus { 28 | 29 | String getServerHost(); 30 | 31 | final class Builder extends BaseBuilder { 32 | 33 | InetSocketAddress endpoint; 34 | 35 | public Builder() { 36 | errorListener = new PrintingErrorListener(); 37 | } 38 | 39 | public Builder connect(String host, int port) { 40 | endpoint = new InetSocketAddress(host, port); 41 | return this; 42 | } 43 | 44 | public Builder connect(int port) { 45 | return connect("localhost", port); 46 | } 47 | 48 | @Override 49 | public EventBusClient build() throws EventBusException { 50 | if (endpoint == null) { 51 | connect(System.getProperty("ipc.bus.host", "localhost"), Integer.parseInt(System.getProperty("ipc.bus.port", "56789"))); 52 | } 53 | try { 54 | Socket socket = SocketFactory.getDefault().createSocket(); 55 | socket.connect(endpoint); 56 | return busId == null ? new DefaultEventBusClient(socket, errorListener, listeners) : new DefaultEventBusClient(busId, socket, errorListener, listeners); 57 | } catch (IOException e) { 58 | throw new EventBusIOException("Bad endpoint: " + endpoint.getHostName() + ":" + endpoint.getPort() + " : " + e.getMessage(), e); 59 | } 60 | } 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/EventBusServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.terracotta.ipceventbus.event; 18 | 19 | import javax.net.ServerSocketFactory; 20 | import java.io.IOException; 21 | import java.net.InetSocketAddress; 22 | import java.net.ServerSocket; 23 | 24 | /** 25 | * @author Mathieu Carbou 26 | */ 27 | public interface EventBusServer extends RemoteEventBus { 28 | 29 | int getClientCount(); 30 | 31 | final class Builder extends BaseBuilder { 32 | 33 | int port = Integer.parseInt(System.getProperty("ipc.bus.port", "56789")); 34 | String address = "0.0.0.0"; 35 | 36 | public Builder() { 37 | errorListener = new PrintingErrorListener(); 38 | } 39 | 40 | public Builder bind(String address) { 41 | this.address = address; 42 | return this; 43 | } 44 | 45 | public Builder listen(int port) { 46 | this.port = port; 47 | return this; 48 | } 49 | 50 | public Builder listenRandom() { 51 | port = 0; 52 | return this; 53 | } 54 | 55 | @Override 56 | public EventBusServer build() throws EventBusException { 57 | try { 58 | ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(); 59 | serverSocket.bind(new InetSocketAddress(address, port)); 60 | return new DefaultEventBusServer(busId != null ? busId : (serverSocket.getInetAddress().getHostAddress() + ":" + serverSocket.getLocalPort()), serverSocket, errorListener, listeners); 61 | } catch (IOException e) { 62 | throw new EventBusIOException("Cannot bind on " + address + ":" + port + " : " + e.getMessage(), e); 63 | } 64 | } 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/event/RemoteEventBusTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | import org.junit.After; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.junit.runners.JUnit4; 25 | 26 | import java.io.IOException; 27 | 28 | import static org.junit.Assert.assertEquals; 29 | import static org.junit.Assert.assertFalse; 30 | 31 | /** 32 | * @author Mathieu Carbou 33 | */ 34 | @RunWith(JUnit4.class) 35 | public class RemoteEventBusTest extends AbstractEventBusTest { 36 | 37 | EventBusServer peer; 38 | 39 | @Before 40 | public void init() { 41 | peer = new EventBusServer.Builder() 42 | .onError(errorListener) 43 | .listenRandom() 44 | .build(); 45 | peer.on(new EventListenerSniffer("server")); 46 | 47 | eventBus = new EventBusClient.Builder() 48 | .onError(errorListener) 49 | .connect(peer.getServerPort()) 50 | .build(); 51 | eventBus.on(new EventListenerSniffer("eventBus")); 52 | } 53 | 54 | @After 55 | public void close() throws IOException { 56 | peer.close(); 57 | eventBus.close(); 58 | } 59 | 60 | @Test 61 | public void server_has_toString() { 62 | assertEquals("EventBusServer:0.0.0.0:" + peer.getServerPort(), peer.toString()); 63 | } 64 | 65 | @Test 66 | public void bus_has_host() { 67 | assertEquals("localhost", eventBus.getServerHost()); 68 | } 69 | 70 | @Test 71 | public void bus_has_port() { 72 | assertEquals(peer.getServerPort(), eventBus.getServerPort()); 73 | } 74 | 75 | @Test 76 | public void bus_has_isClosed() { 77 | assertFalse(peer.isClosed()); 78 | assertFalse(eventBus.isClosed()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/io/MultiplexTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.io; 19 | 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.JUnit4; 23 | 24 | import java.io.ByteArrayOutputStream; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | import static org.junit.Assert.assertFalse; 28 | import static org.junit.Assert.assertTrue; 29 | import static org.junit.Assert.fail; 30 | 31 | /** 32 | * @author Mathieu Carbou 33 | */ 34 | @RunWith(JUnit4.class) 35 | public class MultiplexTest { 36 | 37 | @Test 38 | public void can_multiplex_streams() throws Exception { 39 | ByteArrayOutputStream baos1 = new ByteArrayOutputStream(); 40 | ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); 41 | final MultiplexOutputStream plex = new MultiplexOutputStream() 42 | .addOutputStream(baos1) 43 | .addOutputStream(baos2) 44 | .addOutputStream(System.out); 45 | assertEquals(3, plex.streamCount()); 46 | assertFalse(plex.isEmpty()); 47 | assertTrue(plex.getOutputStreams().contains(System.out)); 48 | assertTrue(plex.getOutputStreams().contains(baos1)); 49 | assertTrue(plex.getOutputStreams().contains(baos2)); 50 | Thread writer = new Thread() { 51 | @Override 52 | public void run() { 53 | try { 54 | for (int i = 1; i <= 5; i++) { 55 | plex.write(("put-" + i + "\n").getBytes()); 56 | } 57 | plex.close(); 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | fail(); 61 | } 62 | } 63 | }; 64 | writer.start(); 65 | writer.join(); 66 | assertEquals("put-1\nput-2\nput-3\nput-4\nput-5\n", new String(baos1.toByteArray())); 67 | assertEquals("put-1\nput-2\nput-3\nput-4\nput-5\n", new String(baos2.toByteArray())); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/BaseBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.terracotta.ipceventbus.event; 18 | 19 | import java.util.UUID; 20 | 21 | /** 22 | * @author Mathieu Carbou 23 | */ 24 | public class BaseBuilder> { 25 | 26 | String busId; 27 | ErrorListener errorListener = new RethrowingErrorListener(); 28 | Listeners listeners = new Listeners(); 29 | 30 | BaseBuilder() { 31 | } 32 | 33 | /** 34 | * Identifies a bus. If no ID is given, a generated one will be given. 35 | * 36 | * @param busId An identifier to use for this bus 37 | * @return this builder 38 | */ 39 | public T id(String busId) { 40 | this.busId = busId; 41 | return (T) this; 42 | } 43 | 44 | /** 45 | * Register a new listener for an event 46 | * 47 | * @param event The event name 48 | * @param listener The listener to register 49 | */ 50 | public T on(String event, EventListener listener) { 51 | listeners.on(event).add(listener); 52 | return (T) this; 53 | } 54 | 55 | /** 56 | * Register a new listener for all event 57 | * 58 | * @param listener The listener to register 59 | */ 60 | public T on(EventListener listener) { 61 | listeners.on("").add(listener); 62 | return (T) this; 63 | } 64 | 65 | /** 66 | * Registers an {@link ErrorListener} to handle exceptions thrown by {@link EventListener}. By default, exceptions are rethrown. 67 | * 68 | * @param listener The listener to use. Some default implementations are provided. 69 | * @return this builder 70 | */ 71 | public T onError(ErrorListener listener) { 72 | this.errorListener = listener; 73 | return (T) this; 74 | } 75 | 76 | public EventBus build() throws EventBusException { 77 | return new DefaultEventBus(busId != null ? busId : UUID.randomUUID().toString(), errorListener, listeners); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/DefaultEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | import java.io.Serializable; 21 | 22 | /** 23 | * @author Mathieu Carbou 24 | */ 25 | class DefaultEvent implements Serializable, Event { 26 | 27 | private static final long serialVersionUID = -4856946361193249489L; 28 | 29 | private final String source; 30 | private final String name; 31 | private final long timestamp = System.currentTimeMillis(); 32 | private final Object data; 33 | 34 | DefaultEvent(String source, String name) { 35 | this(source, name, null); 36 | } 37 | 38 | DefaultEvent(String source, String name, Object data) { 39 | Assert.legalEventName(name); 40 | this.source = source; 41 | this.name = name; 42 | this.data = data; 43 | } 44 | 45 | @Override 46 | public boolean isUserEvent() { 47 | return !name.startsWith("eventbus."); 48 | } 49 | 50 | @Override 51 | public String getSource() { 52 | return source; 53 | } 54 | 55 | 56 | @Override 57 | public String getName() { 58 | return name; 59 | } 60 | 61 | 62 | @Override 63 | public long getTimestamp() { 64 | return timestamp; 65 | } 66 | 67 | @Override 68 | public Object getData() { 69 | return data; 70 | } 71 | 72 | @Override 73 | public T getData(Class type) { 74 | return type.cast(getData()); 75 | } 76 | 77 | @Override 78 | public T getData(Class type, T defaultValue) { 79 | T t = getData(type); 80 | return t == null ? defaultValue : t; 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | final StringBuilder sb = new StringBuilder("Event{"); 86 | sb.append("name='").append(name).append('\''); 87 | sb.append(", source=").append(source); 88 | sb.append(", data=").append(data); 89 | sb.append('}'); 90 | return sb.toString(); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/io/Pipe.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.io; 19 | 20 | import java.io.Closeable; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.io.InterruptedIOException; 24 | import java.io.OutputStream; 25 | import java.util.logging.Level; 26 | import java.util.logging.Logger; 27 | 28 | public final class Pipe implements Closeable { 29 | 30 | private static final Logger LOGGER = Logger.getLogger(Pipe.class.getName()); 31 | 32 | private volatile Thread pipe; 33 | 34 | public Pipe(String name, final InputStream in, final OutputStream out) { 35 | this(name, in, out, 8192); 36 | } 37 | 38 | public Pipe(String name, final InputStream in, final OutputStream out, final int bufferSize) { 39 | this.pipe = new Thread(name) { 40 | @Override 41 | public void run() { 42 | byte[] buffer = new byte[bufferSize]; 43 | int len; 44 | try { 45 | while (!Thread.currentThread().isInterrupted() && (len = in.read(buffer)) != -1) { 46 | out.write(buffer, 0, len); 47 | out.flush(); 48 | } 49 | } catch (InterruptedIOException ignored) { 50 | } catch (IOException e) { 51 | LOGGER.log(Level.WARNING, "Error reading/writing streams: " + e.getMessage(), e); 52 | } finally { 53 | close(); 54 | } 55 | } 56 | }; 57 | pipe.setDaemon(true); 58 | pipe.start(); 59 | } 60 | 61 | public void waitFor() throws InterruptedException { 62 | Thread t = this.pipe; 63 | if (t != null) { 64 | t.join(); 65 | close(); 66 | } 67 | } 68 | 69 | /** 70 | * Close the pipe, but not the underlying streams! 71 | */ 72 | public synchronized void close() { 73 | if (pipe != null) { 74 | Thread t = pipe; 75 | pipe = null; 76 | t.interrupt(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/event/RemoteEventBusPeerCommunicationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.terracotta.ipceventbus.event; 18 | 19 | import com.jayway.awaitility.Awaitility; 20 | import org.junit.Test; 21 | 22 | import java.io.IOException; 23 | import java.net.ServerSocket; 24 | import java.util.concurrent.TimeUnit; 25 | import java.util.stream.IntStream; 26 | 27 | public class RemoteEventBusPeerCommunicationTest { 28 | @Test 29 | public void bus_can_communicate_events() throws Throwable { 30 | int port = create(15000, 15500); 31 | RecordingEventListener listener = new RecordingEventListener(); 32 | 33 | final EventBusServer peer1 = new EventBusServer.Builder() 34 | .id("peer1") 35 | .listen(port) 36 | .build(); 37 | 38 | peer1.on(listener); 39 | peer1.on(new EventListenerSniffer("peer1")); 40 | 41 | final EventBusClient peer2 = new EventBusClient.Builder() 42 | .id("peer2") 43 | .connect(port) 44 | .build(); 45 | 46 | peer2.on(listener); 47 | peer2.on(new EventListenerSniffer("peer2")); 48 | 49 | Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> listener.names.stream().anyMatch("eventbus.client.connect"::equals)); 50 | 51 | peer1.trigger("action"); 52 | Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> 2 == listener.userEvents); 53 | 54 | peer2.trigger("action"); 55 | Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> 4 == listener.userEvents); 56 | 57 | peer1.close(); 58 | peer2.close(); 59 | } 60 | 61 | private int create(int startPort, int endPort) { 62 | return IntStream.range(startPort, endPort) 63 | .filter(port -> { 64 | try { 65 | new ServerSocket(port).close(); 66 | return true; 67 | } catch (IOException ex) { 68 | return false; 69 | } 70 | }) 71 | .findFirst() 72 | .orElseThrow(() -> new RuntimeException("no free port found")); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/proc/EventJavaProcessBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import org.terracotta.ipceventbus.event.EventBusServer; 21 | import org.terracotta.ipceventbus.event.EventListener; 22 | 23 | /** 24 | * @author Mathieu Carbou 25 | */ 26 | public class EventJavaProcessBuilder extends JavaProcessBuilder { 27 | 28 | final EventBusServer.Builder eventBusBuilder = new EventBusServer.Builder(); 29 | 30 | public EventJavaProcessBuilder port(int port) { 31 | eventBusBuilder.listen(port); 32 | return this; 33 | } 34 | 35 | public EventJavaProcessBuilder randomPort() { 36 | eventBusBuilder.listenRandom(); 37 | return this; 38 | } 39 | 40 | /** 41 | * Register a new listener for an event 42 | * 43 | * @param event The event name 44 | * @param listener The listener to register 45 | */ 46 | public EventJavaProcessBuilder on(String event, EventListener listener) { 47 | eventBusBuilder.on(event, listener); 48 | return this; 49 | } 50 | 51 | /** 52 | * Register a new listener for all event 53 | * 54 | * @param listener The listener to register 55 | */ 56 | public EventJavaProcessBuilder on(EventListener listener) { 57 | eventBusBuilder.on(listener); 58 | return this; 59 | } 60 | 61 | @Override 62 | public T build() { 63 | EventBusServer eventBusServer = eventBusBuilder.build(); 64 | 65 | addJvmProp("ipc.bus.host", "localhost"); 66 | addJvmProp("ipc.bus.port", Integer.toString(eventBusServer.getServerPort())); 67 | addClasspath(Bus.class); 68 | if (debug) { 69 | addJvmProp("ipc.bus.debug", "true"); 70 | } 71 | if (mainClass != null) { 72 | addJvmProp("ipc.bus.mainClass", mainClass); 73 | mainClass(Boot.class.getName()); 74 | } 75 | 76 | buildCommand(); 77 | return (T) new EventJavaProcess(createProcess(), 78 | pipeStdout, pipeStderr, pipeStdin, recordStdout, recordStderr, command, workingDir, 79 | javaHome, javaExecutable, jvmArgs, classpath, mainClass, arguments, jvmProps, 80 | debug, eventBusServer); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/proc/JavaProcessTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.JUnit4; 23 | import org.terracotta.ipceventbus.ThreadUtil; 24 | 25 | import static org.junit.Assert.assertEquals; 26 | import static org.junit.Assert.assertTrue; 27 | 28 | /** 29 | * @author Mathieu Carbou 30 | */ 31 | @RunWith(JUnit4.class) 32 | public class JavaProcessTest { 33 | 34 | @Test 35 | public void test_launch_java_process() throws InterruptedException { 36 | JavaProcess proc = JavaProcess.newBuilder() 37 | .mainClass(Echo.class.getName()) 38 | .addClasspath(Echo.class) 39 | .addClasspath(ThreadUtil.class) 40 | .addClasspath(JUnit4.class) 41 | .arguments("one", "two") 42 | .addJvmProp("my.prop", "world") 43 | .addJvmArg("-Xmx512m") 44 | .env("VAR", "Hello") 45 | .pipeStdout() 46 | .pipeStderr() 47 | .recordStdout() 48 | .recordStderr() 49 | .build(); 50 | 51 | System.out.println(proc.getCommand()); 52 | assertEquals(0, proc.waitFor()); 53 | assertEquals("Hello\n" + 54 | "world\n" + 55 | "one\n" + 56 | "two\n", proc.getRecordedStdoutText()); 57 | assertEquals("", proc.getRecordedStderrText()); 58 | } 59 | 60 | @Test 61 | public void test_launch_failing_java_process() throws InterruptedException { 62 | JavaProcess proc = JavaProcess.newBuilder() 63 | .mainClass(EchoFail.class.getName()) 64 | .addClasspath(EchoFail.class) 65 | .addClasspath(ThreadUtil.class) 66 | .addClasspath(JUnit4.class) 67 | .arguments("one", "two") 68 | .addJvmProp("my.prop", "world") 69 | .addJvmArg("-Xmx512m") 70 | .env("VAR", "Hello") 71 | .recordStdout() 72 | .recordStderr() 73 | .build(); 74 | 75 | System.out.println(proc.getCommand()); 76 | assertEquals(1, proc.waitFor()); 77 | assertEquals("Hello\n" + 78 | "world\n" + 79 | "one\n" + 80 | "two\n", proc.getRecordedStdoutText()); 81 | assertTrue(proc.getRecordedStderrText().contains("Exception in thread \"main\" java.lang.AssertionError: message")); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/proc/Boot.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 21 | import org.terracotta.ipceventbus.event.Event; 22 | import org.terracotta.ipceventbus.event.EventListener; 23 | 24 | import java.lang.reflect.InvocationTargetException; 25 | import java.util.concurrent.atomic.AtomicBoolean; 26 | 27 | /** 28 | * @author Mathieu Carbou 29 | */ 30 | public final class Boot { 31 | public static void main(String[] args) throws Throwable { 32 | 33 | if (System.getProperty("ipc.bus.mainClass") == null) { 34 | throw new IllegalStateException("No main class specified"); 35 | } 36 | 37 | Class mainClass = Class.forName(System.getProperty("ipc.bus.mainClass")); 38 | 39 | if (Bus.isDebug()) { 40 | System.out.println("[" + Boot.class.getSimpleName() + "] Starting " + mainClass.getName() + "..."); 41 | } 42 | 43 | final AtomicBoolean firedExiting = new AtomicBoolean(); 44 | 45 | Bus.get().on("process.exit", new EventListener() { 46 | @Override 47 | @SuppressFBWarnings("DM_EXIT") 48 | public void onEvent(Event e) { 49 | if (firedExiting.compareAndSet(false, true)) { 50 | Bus.get().trigger("process.exiting", Bus.getCurrentPid()); 51 | } 52 | System.exit(e.getData(Integer.class, 0)); 53 | } 54 | }); 55 | 56 | Runtime.getRuntime().addShutdownHook(new Thread() { 57 | @Override 58 | public void run() { 59 | if (firedExiting.compareAndSet(false, true)) { 60 | Bus.get().trigger("process.exiting", Bus.getCurrentPid()); 61 | } 62 | } 63 | }); 64 | 65 | try { 66 | mainClass.getDeclaredMethod("main", String[].class).invoke(null, new Object[]{args}); 67 | } catch (InvocationTargetException e) { 68 | throw e.getTargetException(); 69 | } finally { 70 | Thread current = Thread.currentThread(); 71 | for (Thread thread : Thread.getAllStackTraces().keySet()) { 72 | if (!thread.isDaemon() && current != thread) { 73 | try { 74 | thread.join(); 75 | } catch (InterruptedException ignored) { 76 | } 77 | } 78 | } 79 | if (firedExiting.compareAndSet(false, true)) { 80 | Bus.get().trigger("process.exiting"); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/proc/JavaProcess.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import java.io.File; 21 | import java.io.InputStream; 22 | import java.io.OutputStream; 23 | import java.util.Collections; 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | /** 28 | * @author Mathieu Carbou 29 | */ 30 | public class JavaProcess extends AnyProcess { 31 | 32 | private final File javaHome; 33 | private final File javaExecutable; 34 | private final List jvmArgs; 35 | private final Map jvmProperties; 36 | private final List classpath; 37 | private final String mainClass; 38 | private final List arguments; 39 | 40 | public JavaProcess(Process process, 41 | OutputStream pipeStdout, OutputStream pipeStderr, InputStream pipeStdin, boolean collectStdout, boolean collectStderr, List command, File workingDir, 42 | File javaHome, File javaExecutable, List jvmArgs, List classpath, String mainClass, List arguments, Map jvmProperties) { 43 | super(process, pipeStdout, pipeStderr, pipeStdin, collectStdout, collectStderr, command, workingDir); 44 | this.javaHome = javaHome; 45 | this.javaExecutable = javaExecutable; 46 | this.jvmArgs = Collections.unmodifiableList(jvmArgs); 47 | this.classpath = Collections.unmodifiableList(classpath); 48 | this.mainClass = mainClass; 49 | this.arguments = Collections.unmodifiableList(arguments); 50 | this.jvmProperties = Collections.unmodifiableMap(jvmProperties); 51 | } 52 | 53 | public final Map getJvmProperties() { 54 | return jvmProperties; 55 | } 56 | 57 | public final File getJavaHome() { 58 | return javaHome; 59 | } 60 | 61 | public final File getJavaExecutable() { 62 | return javaExecutable; 63 | } 64 | 65 | public final List getJvmArgs() { 66 | return jvmArgs; 67 | } 68 | 69 | public final List getClasspath() { 70 | return classpath; 71 | } 72 | 73 | public final String getMainClass() { 74 | return mainClass; 75 | } 76 | 77 | public final List getArguments() { 78 | return arguments; 79 | } 80 | 81 | public static JavaProcessBuilder newBuilder() { 82 | return new JavaProcessBuilder(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/io/PipeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.io; 19 | 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.JUnit4; 23 | import org.terracotta.ipceventbus.ThreadUtil; 24 | 25 | import java.io.ByteArrayOutputStream; 26 | import java.io.IOException; 27 | import java.io.PipedInputStream; 28 | import java.io.PipedOutputStream; 29 | import java.util.concurrent.CountDownLatch; 30 | 31 | import static org.junit.Assert.assertEquals; 32 | import static org.junit.Assert.fail; 33 | 34 | /** 35 | * @author Mathieu Carbou 36 | */ 37 | @RunWith(JUnit4.class) 38 | public class PipeTest { 39 | 40 | @Test 41 | public void pipe_test() throws Exception { 42 | ByteArrayOutputStream collected = new ByteArrayOutputStream(); 43 | PipedInputStream in = new PipedInputStream(); 44 | final PipedOutputStream out = new PipedOutputStream(in); 45 | new Thread() { 46 | @Override 47 | public void run() { 48 | try { 49 | for (int i = 1; i <= 5; i++) { 50 | out.write(("put-" + i + "\n").getBytes()); 51 | ThreadUtil.minimumSleep(500); 52 | } 53 | out.close(); 54 | } catch (Exception e) { 55 | e.printStackTrace(); 56 | fail(); 57 | } 58 | } 59 | }.start(); 60 | Pipe pipe = new Pipe("name", in, collected); 61 | pipe.waitFor(); 62 | assertEquals("put-1\nput-2\nput-3\nput-4\nput-5\n", new String(collected.toByteArray())); 63 | } 64 | 65 | @Test 66 | public void pipe_interruption() throws Exception { 67 | ByteArrayOutputStream collected = new ByteArrayOutputStream(); 68 | PipedInputStream in = new PipedInputStream(); 69 | final PipedOutputStream out = new PipedOutputStream(in); 70 | final CountDownLatch brake = new CountDownLatch(2); 71 | new Thread() { 72 | { 73 | setDaemon(true); 74 | } 75 | 76 | @Override 77 | public void run() { 78 | try { 79 | for (int i = 1; i <= 5; i++) { 80 | out.write(("a").getBytes()); 81 | out.flush(); 82 | brake.countDown(); 83 | ThreadUtil.minimumSleep(500); 84 | } 85 | fail(); 86 | } catch (Exception e) { 87 | assertEquals(IOException.class, e.getClass()); 88 | } 89 | } 90 | }.start(); 91 | Pipe pipe = new Pipe("name", in, collected); 92 | brake.await(); 93 | pipe.close(); 94 | assertEquals("aa", new String(collected.toByteArray())); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/io/MultiplexOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.io; 19 | 20 | import java.io.IOException; 21 | import java.io.OutputStream; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | /** 26 | * @author Mathieu Carbou 27 | */ 28 | public class MultiplexOutputStream extends OutputStream { 29 | 30 | private List streams = new ArrayList(2); 31 | 32 | public MultiplexOutputStream() { 33 | } 34 | 35 | public MultiplexOutputStream(OutputStream os) { 36 | addOutputStream(os); 37 | } 38 | 39 | public List getOutputStreams() { 40 | return streams; 41 | } 42 | 43 | public MultiplexOutputStream addOutputStream(OutputStream os) { 44 | streams.add(os); 45 | return this; 46 | } 47 | 48 | public boolean isEmpty() { 49 | return streams.isEmpty(); 50 | } 51 | 52 | public int streamCount() { 53 | return streams.size(); 54 | } 55 | 56 | @Override 57 | public void close() { 58 | } 59 | 60 | @Override 61 | public void flush() throws IOException { 62 | IOException ioe = null; 63 | for (OutputStream stream : streams) { 64 | try { 65 | stream.flush(); 66 | } catch (IOException e) { 67 | ioe = e; 68 | } 69 | } 70 | if (ioe != null) { 71 | throw ioe; 72 | } 73 | } 74 | 75 | @Override 76 | public void write(int b) throws IOException { 77 | IOException ioe = null; 78 | for (OutputStream stream : streams) { 79 | try { 80 | stream.write(b); 81 | } catch (IOException e) { 82 | ioe = e; 83 | } 84 | } 85 | if (ioe != null) { 86 | throw ioe; 87 | } 88 | } 89 | 90 | @Override 91 | public void write(byte[] b) throws IOException { 92 | IOException ioe = null; 93 | for (OutputStream stream : streams) { 94 | try { 95 | stream.write(b); 96 | } catch (IOException e) { 97 | ioe = e; 98 | } 99 | } 100 | if (ioe != null) { 101 | throw ioe; 102 | } 103 | } 104 | 105 | @Override 106 | public void write(byte[] b, int off, int len) throws IOException { 107 | IOException ioe = null; 108 | for (OutputStream stream : streams) { 109 | try { 110 | stream.write(b, off, len); 111 | } catch (IOException e) { 112 | ioe = e; 113 | } 114 | } 115 | if (ioe != null) { 116 | throw ioe; 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/DefaultEventBus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | /** 21 | * @author Mathieu Carbou 22 | */ 23 | class DefaultEventBus implements EventBus { 24 | 25 | private final String uuid; 26 | private final ErrorListener errorListener; 27 | protected final Listeners listeners; 28 | 29 | DefaultEventBus(String uuid, ErrorListener errorListener) { 30 | this(uuid, errorListener, new Listeners()); 31 | } 32 | 33 | DefaultEventBus(String uuid, ErrorListener errorListener, Listeners initialListeners) { 34 | this.uuid = uuid; 35 | this.errorListener = errorListener; 36 | this.listeners = new Listeners(initialListeners); 37 | } 38 | 39 | @Override 40 | public String getId() { 41 | return uuid; 42 | } 43 | 44 | @Override 45 | public void on(String event, EventListener listener) { 46 | Assert.legalEventName(event); 47 | listeners.on(event).add(listener); 48 | } 49 | 50 | @Override 51 | public void on(EventListener listener) { 52 | listeners.on("").add(listener); 53 | } 54 | 55 | @Override 56 | public void trigger(String name) { 57 | trigger(name, null); 58 | } 59 | 60 | @Override 61 | public void trigger(String name, Object data) { 62 | Assert.legalEventName(name); 63 | Assert.notInternalName(name); 64 | Event event = new DefaultEvent(getId(), name, data); 65 | sendLocal(event); 66 | } 67 | 68 | void sendLocal(Event event) { 69 | for (EventListener listener : listeners.on(event.getName())) { 70 | try { 71 | listener.onEvent(event); 72 | } catch (Throwable e) { 73 | errorListener.onError(event, listener, e); 74 | } 75 | } 76 | for (EventListener listener : listeners.on("")) { 77 | try { 78 | listener.onEvent(event); 79 | } catch (Throwable e) { 80 | errorListener.onError(event, listener, e); 81 | } 82 | } 83 | } 84 | 85 | @Override 86 | public void unbind(String event) { 87 | Assert.legalEventName(event); 88 | Assert.notInternalName(event); 89 | listeners.removeAll(event); 90 | } 91 | 92 | @Override 93 | public void unbind(EventListener listener) { 94 | listeners.removeAll(listener); 95 | } 96 | 97 | @Override 98 | public void unbind(String event, EventListener listener) { 99 | Assert.legalEventName(event); 100 | listeners.remove(event, listener); 101 | } 102 | 103 | @Override 104 | public String toString() { 105 | return EventBus.class.getSimpleName() + ":" + getId(); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/proc/EventJavaProcess.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import org.terracotta.ipceventbus.event.EventBusClient; 21 | import org.terracotta.ipceventbus.event.EventBusServer; 22 | import org.terracotta.ipceventbus.event.EventListener; 23 | import org.terracotta.ipceventbus.event.EventListenerSniffer; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.io.OutputStream; 29 | import java.util.List; 30 | import java.util.Map; 31 | 32 | /** 33 | * @author Mathieu Carbou 34 | */ 35 | public final class EventJavaProcess extends JavaProcess { 36 | 37 | private volatile EventBusServer eventBus; 38 | 39 | public EventJavaProcess(Process process, 40 | OutputStream pipeStdout, OutputStream pipeStderr, InputStream pipeStdin, boolean collectStdout, boolean collectStderr, List command, File workingDir, 41 | File javaHome, File javaExecutable, List jvmArgs, List classpath, String mainClass, List arguments, Map jvmProperties, 42 | boolean debug, EventBusServer eventBusServer) { 43 | super(process, 44 | pipeStdout, pipeStderr, pipeStdin, collectStdout, collectStderr, command, workingDir, 45 | javaHome, javaExecutable, jvmArgs, classpath, mainClass, arguments, jvmProperties); 46 | String pid = getCurrentPid(); 47 | // try to connect 48 | this.eventBus = eventBusServer; 49 | if (debug) { 50 | eventBus.on(new EventListenerSniffer(pid)); 51 | } 52 | } 53 | 54 | @Override 55 | protected void onDestroyed() { 56 | if (eventBus != null) { 57 | eventBus.trigger("process.destroyed"); 58 | } 59 | close(); 60 | } 61 | 62 | @Override 63 | protected void onTerminated() { 64 | if (eventBus != null) { 65 | eventBus.trigger("process.exited"); 66 | } 67 | close(); 68 | } 69 | 70 | private void close() { 71 | try { 72 | if (eventBus != null && eventBus instanceof EventBusClient && !((EventBusClient) eventBus).isClosed()) { 73 | ((EventBusClient) eventBus).close(); 74 | } 75 | } catch (IOException ignored) { 76 | } 77 | } 78 | 79 | public final boolean isEventBusConnected() { 80 | return eventBus.getClientCount() > 0; 81 | } 82 | 83 | public final String getEventBusServerHost() { 84 | return eventBus.getServerHost(); 85 | } 86 | 87 | public final int getEventBusServerPort() { 88 | return eventBus.getServerPort(); 89 | } 90 | 91 | public final String getEventBusId() { 92 | return eventBus.getId(); 93 | } 94 | 95 | public final void on(String event, org.terracotta.ipceventbus.event.EventListener listener) { 96 | eventBus.on(event, listener); 97 | } 98 | 99 | public final void unbind(String event) { 100 | eventBus.unbind(event); 101 | } 102 | 103 | public final void on(EventListener listener) { 104 | eventBus.on(listener); 105 | } 106 | 107 | public final void unbind(EventListener listener) { 108 | eventBus.unbind(listener); 109 | } 110 | 111 | public final void unbind(String event, EventListener listener) { 112 | eventBus.unbind(event, listener); 113 | } 114 | 115 | public final void trigger(String name) { 116 | eventBus.trigger(name); 117 | } 118 | 119 | public final void trigger(String name, Object data) { 120 | eventBus.trigger(name, data); 121 | } 122 | 123 | public static EventJavaProcessBuilder newBuilder() { 124 | return new EventJavaProcessBuilder(); 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/proc/AnyProcessBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.io.OutputStream; 24 | import java.util.ArrayList; 25 | import java.util.Arrays; 26 | import java.util.LinkedHashMap; 27 | import java.util.List; 28 | import java.util.Map; 29 | import java.util.stream.Collectors; 30 | 31 | import static java.lang.String.join; 32 | 33 | /** 34 | * @author Mathieu Carbou 35 | */ 36 | public class AnyProcessBuilder { 37 | 38 | File workingDir = new File("."); 39 | InputStream pipeStdin; 40 | OutputStream pipeStdout; 41 | OutputStream pipeStderr; 42 | boolean redirectStderr; 43 | Map env = new LinkedHashMap<>(System.getenv()); 44 | boolean recordStdout; 45 | boolean recordStderr; 46 | List command = new ArrayList<>(); 47 | boolean debug; 48 | 49 | public AnyProcessBuilder debug() { 50 | this.debug = true; 51 | return this; 52 | } 53 | 54 | public final AnyProcessBuilder command(String... command) { 55 | this.command = new ArrayList<>(Arrays.asList(command)); 56 | return this; 57 | } 58 | 59 | public final AnyProcessBuilder pipeStdin(InputStream pipeStdin) { 60 | this.pipeStdin = pipeStdin; 61 | return this; 62 | } 63 | 64 | public final AnyProcessBuilder pipeStdin() { 65 | return pipeStdin(System.in); 66 | } 67 | 68 | public final AnyProcessBuilder pipeStdout(OutputStream pipeStdout) { 69 | this.pipeStdout = pipeStdout; 70 | return this; 71 | } 72 | 73 | public final AnyProcessBuilder pipeStdout() { 74 | return pipeStdout(System.out); 75 | } 76 | 77 | public final AnyProcessBuilder pipeStderr(OutputStream pipeStderr) { 78 | this.pipeStderr = pipeStderr; 79 | return this; 80 | } 81 | 82 | public final AnyProcessBuilder pipeStderr() { 83 | return pipeStderr(System.err); 84 | } 85 | 86 | public final AnyProcessBuilder workingDir(File workingDirectory) { 87 | this.workingDir = workingDirectory; 88 | return this; 89 | } 90 | 91 | public final AnyProcessBuilder redirectStderr() { 92 | this.redirectStderr = true; 93 | return this; 94 | } 95 | 96 | public final AnyProcessBuilder env(Map newEnv) { 97 | this.env = new LinkedHashMap<>(newEnv); 98 | return this; 99 | } 100 | 101 | public final AnyProcessBuilder env(String key, String value) { 102 | this.env.put(key, value); 103 | return this; 104 | } 105 | 106 | public final AnyProcessBuilder recordStdout() { 107 | this.recordStdout = true; 108 | return this; 109 | } 110 | 111 | public final AnyProcessBuilder recordStderr() { 112 | this.recordStderr = true; 113 | return this; 114 | } 115 | 116 | public T build() { 117 | return (T) new AnyProcess(createProcess(), pipeStdout, pipeStderr, pipeStdin, recordStdout, recordStderr, command, workingDir); 118 | } 119 | 120 | protected Process createProcess() { 121 | if (command.isEmpty()) { 122 | throw new IllegalArgumentException("Missing command"); 123 | } 124 | java.lang.ProcessBuilder builder = new java.lang.ProcessBuilder() 125 | .command(command) 126 | .directory(workingDir) 127 | .redirectErrorStream(redirectStderr); 128 | Map processEnv = builder.environment(); 129 | processEnv.clear(); 130 | processEnv.putAll(this.env); 131 | try { 132 | Process process = builder.start(); 133 | if (debug) { 134 | System.out.println("[" + AnyProcess.getCurrentPid() + "] Started process " + AnyProcess.getPid(process) + ": " + join(" ", builder.command())); 135 | } 136 | return process; 137 | } catch (IOException e) { 138 | throw new IllegalArgumentException("Unable to start " + command, e); 139 | } 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/DefaultEventBusClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | import java.io.IOException; 21 | import java.io.ObjectInputStream; 22 | import java.io.ObjectOutputStream; 23 | import java.net.Socket; 24 | import java.util.concurrent.CountDownLatch; 25 | import java.util.concurrent.atomic.AtomicReference; 26 | 27 | /** 28 | * @author Mathieu Carbou 29 | */ 30 | class DefaultEventBusClient extends DefaultEventBus implements EventBusClient { 31 | 32 | private final AtomicReference socket; 33 | private ObjectOutputStream outputStream; 34 | private ObjectInputStream inputStream; 35 | private Thread receiver; 36 | 37 | DefaultEventBusClient(Socket socket, ErrorListener listener, Listeners initialListeners) { 38 | this(socket.getLocalAddress().getHostName() + ":" + socket.getLocalPort(), socket, listener, initialListeners); 39 | } 40 | 41 | DefaultEventBusClient(String uuid, Socket socket, ErrorListener listener) { 42 | this(uuid, socket, listener, new Listeners()); 43 | } 44 | 45 | DefaultEventBusClient(String uuid, Socket socket, ErrorListener listener, Listeners initialListeners) { 46 | super(uuid, listener, initialListeners); 47 | this.socket = new AtomicReference<>(socket); 48 | try { 49 | this.outputStream = new ObjectOutputStream(socket.getOutputStream()); 50 | this.inputStream = new ObjectInputStream(socket.getInputStream()); 51 | } catch (IOException e) { 52 | close(); 53 | throw new EventBusIOException("Bad socket: " + socket + " : " + e.getMessage(), e); 54 | } 55 | final CountDownLatch receiving = new CountDownLatch(1); 56 | receiver = new Thread("reader@" + getId()) { 57 | @Override 58 | public void run() { 59 | receiving.countDown(); 60 | while (!Thread.currentThread().isInterrupted() && !isClosed()) { 61 | Event event = null; 62 | try { 63 | event = (Event) inputStream.readObject(); 64 | } catch (IOException | ClassNotFoundException e) { 65 | sendLocal(new DefaultEvent(DefaultEventBusClient.this.getId(), "eventbus.client.error", e)); 66 | 67 | close(); 68 | } 69 | if (event != null && "eventbus.event".equals(event.getName())) { 70 | sendLocal(event.getData(Event.class)); 71 | } 72 | } 73 | } 74 | }; 75 | receiver.setDaemon(true); 76 | receiver.start(); 77 | try { 78 | receiving.await(); 79 | } catch (InterruptedException e) { 80 | Thread.currentThread().interrupt(); 81 | throw new IllegalStateException(e); 82 | } 83 | } 84 | 85 | @Override 86 | public void close() { 87 | if (!isClosed()) { 88 | Socket s = socket.get(); 89 | if (s != null && socket.compareAndSet(s, null)) { 90 | try { 91 | s.close(); 92 | } catch (IOException ignored) { 93 | } 94 | outputStream = null; 95 | inputStream = null; 96 | if (receiver != null) { 97 | receiver.interrupt(); 98 | receiver = null; 99 | } 100 | sendLocal(new DefaultEvent(getId(), "eventbus.client.disconnect")); 101 | } 102 | } 103 | } 104 | 105 | @Override 106 | public int getServerPort() { 107 | return socket.get().getPort(); 108 | } 109 | 110 | @Override 111 | public String getServerHost() { 112 | return socket.get().getInetAddress().getHostName(); 113 | } 114 | 115 | @Override 116 | public boolean isClosed() { 117 | Socket s = socket.get(); 118 | return s == null || s.isClosed(); 119 | } 120 | 121 | @Override 122 | public void trigger(String name, Object data) { 123 | Assert.legalEventName(name); 124 | Assert.notInternalName(name); 125 | Event event = new DefaultEvent(getId(), name, data); 126 | sendLocal(event); 127 | sendRemote(event); 128 | } 129 | 130 | void sendRemote(Event event) { 131 | if (!isClosed()) { 132 | try { 133 | outputStream.writeObject(new DefaultEvent(getId(), "eventbus.event", event)); 134 | outputStream.flush(); 135 | } catch (IOException e) { 136 | close(); 137 | } 138 | } 139 | } 140 | 141 | @Override 142 | public String toString() { 143 | return EventBusClient.class.getSimpleName() + ":" + getId(); 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/event/DefaultEventBusServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | import java.io.IOException; 21 | import java.net.InetSocketAddress; 22 | import java.net.ServerSocket; 23 | import java.net.Socket; 24 | import java.util.ArrayList; 25 | import java.util.Collection; 26 | import java.util.LinkedList; 27 | import java.util.concurrent.CountDownLatch; 28 | import java.util.concurrent.atomic.AtomicReference; 29 | import java.util.concurrent.locks.ReadWriteLock; 30 | import java.util.concurrent.locks.ReentrantReadWriteLock; 31 | 32 | /** 33 | * @author Mathieu Carbou 34 | */ 35 | final class DefaultEventBusServer extends DefaultEventBus implements EventBusServer { 36 | 37 | private final Collection clients = new LinkedList(); 38 | private final ReadWriteLock clientsLock = new ReentrantReadWriteLock(); 39 | private final AtomicReference serverSocket = new AtomicReference(); 40 | private Thread acceptor; 41 | 42 | DefaultEventBusServer(String uuid, ServerSocket serverSocket, final ErrorListener errorListener, final Listeners listeners) { 43 | super(uuid, errorListener, listeners); 44 | this.serverSocket.set(serverSocket); 45 | final EventListener listener = new EventListener() { 46 | @Override 47 | public void onEvent(Event e) { 48 | sendLocal(e); 49 | if (!e.isUserEvent() && "eventbus.client.disconnect".equals(e.getName())) { 50 | for (DefaultEventBusClient cli : getClients()) { 51 | if (cli.getId().equals(e.getSource())) { 52 | clientsLock.writeLock().lock(); 53 | try { 54 | clients.remove(cli); 55 | } finally { 56 | clientsLock.writeLock().unlock(); 57 | } 58 | break; 59 | } 60 | } 61 | } 62 | } 63 | }; 64 | final CountDownLatch listening = new CountDownLatch(1); 65 | acceptor = new Thread("client-acceptor") { 66 | @Override 67 | public void run() { 68 | listening.countDown(); 69 | while (!Thread.currentThread().isInterrupted() && !isClosed()) { 70 | try { 71 | Socket socket = DefaultEventBusServer.this.serverSocket.get().accept(); 72 | InetSocketAddress address = (InetSocketAddress) socket.getRemoteSocketAddress(); 73 | DefaultEventBusClient client = new DefaultEventBusClient(address.getHostName() + ":" + address.getPort(), socket, errorListener); 74 | client.on(listener); 75 | clientsLock.writeLock().lock(); 76 | try { 77 | clients.add(client); 78 | } finally { 79 | clientsLock.writeLock().unlock(); 80 | } 81 | sendLocal(new DefaultEvent(DefaultEventBusServer.this.getId(), "eventbus.client.connect", client.getId())); 82 | } catch (IOException e) { 83 | close(); 84 | } 85 | } 86 | } 87 | }; 88 | acceptor.setDaemon(true); 89 | acceptor.start(); 90 | try { 91 | listening.await(); 92 | } catch (InterruptedException e) { 93 | Thread.currentThread().interrupt(); 94 | throw new IllegalStateException(e); 95 | } 96 | } 97 | 98 | @Override 99 | public int getServerPort() { 100 | return serverSocket.get().getLocalPort(); 101 | } 102 | 103 | @Override 104 | public String getServerHost() { 105 | return serverSocket.get().getInetAddress().getHostName(); 106 | } 107 | 108 | @Override 109 | public boolean isClosed() { 110 | return serverSocket.get() == null || serverSocket.get().isClosed(); 111 | } 112 | 113 | @Override 114 | public void close() { 115 | if (!isClosed()) { 116 | ServerSocket ss = this.serverSocket.get(); 117 | if (ss != null && this.serverSocket.compareAndSet(ss, null)) { 118 | try { 119 | ss.close(); 120 | } catch (IOException ignored) { 121 | } 122 | acceptor.interrupt(); 123 | acceptor = null; 124 | for (DefaultEventBusClient client : getClients()) { 125 | client.close(); 126 | } 127 | sendLocal(new DefaultEvent(getId(), "eventbus.server.close")); 128 | } 129 | } 130 | } 131 | 132 | @Override 133 | public synchronized void trigger(String name, Object data) { 134 | Assert.legalEventName(name); 135 | Assert.notInternalName(name); 136 | Event event = new DefaultEvent(getId(), name, data); 137 | sendLocal(event); 138 | sendRemote(event); 139 | } 140 | 141 | void sendRemote(Event event) { 142 | clientsLock.readLock().lock(); 143 | try { 144 | for (DefaultEventBusClient client : clients) { 145 | client.sendRemote(event); 146 | } 147 | } finally { 148 | clientsLock.readLock().unlock(); 149 | } 150 | } 151 | 152 | @Override 153 | public String toString() { 154 | return EventBusServer.class.getSimpleName() + ":" + getId(); 155 | } 156 | 157 | private Collection getClients() { 158 | clientsLock.readLock().lock(); 159 | try { 160 | return new ArrayList(clients); 161 | } finally { 162 | clientsLock.readLock().unlock(); 163 | } 164 | } 165 | 166 | @Override 167 | public int getClientCount() { 168 | return getClients().size(); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/proc/JavaProcessBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import java.io.File; 21 | import java.net.URISyntaxException; 22 | import java.net.URL; 23 | import java.util.ArrayList; 24 | import java.util.Arrays; 25 | import java.util.LinkedHashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | /** 30 | * @author Mathieu Carbou 31 | */ 32 | public class JavaProcessBuilder extends AnyProcessBuilder { 33 | 34 | String mainClass; 35 | File javaHome = new File(System.getProperty("java.home")); 36 | File javaExecutable = findJavaExecutable(javaHome); 37 | List jvmArgs = new ArrayList(); 38 | List classpath = new ArrayList(); 39 | List arguments = new ArrayList(); 40 | Map jvmProps = new LinkedHashMap(); 41 | 42 | public final JavaProcessBuilder mainClass(String mainClass) { 43 | this.mainClass = mainClass; 44 | return this; 45 | } 46 | 47 | public final JavaProcessBuilder mainClass(Class mainClass) { 48 | mainClass(mainClass.getName()); 49 | return addClasspath(mainClass); 50 | } 51 | 52 | public final JavaProcessBuilder javaHome(File javaHome) { 53 | this.javaHome = javaHome; 54 | return this; 55 | } 56 | 57 | public final JavaProcessBuilder javaExecutable(File javaExecutable) { 58 | this.javaExecutable = javaExecutable; 59 | return this; 60 | } 61 | 62 | public final JavaProcessBuilder jvmArgs(List jvmArgs) { 63 | this.jvmArgs = jvmArgs; 64 | return this; 65 | } 66 | 67 | public final JavaProcessBuilder addJvmArg(String arg) { 68 | this.jvmArgs.add(arg); 69 | return this; 70 | } 71 | 72 | public final JavaProcessBuilder jvmProps(Map jvmProps) { 73 | this.jvmProps = new LinkedHashMap(jvmProps); 74 | return this; 75 | } 76 | 77 | public final JavaProcessBuilder addJvmProp(String key, String val) { 78 | this.jvmProps.put(key, val); 79 | return this; 80 | } 81 | 82 | public final JavaProcessBuilder classpath(List classpath) { 83 | this.classpath = classpath; 84 | return this; 85 | } 86 | 87 | public final JavaProcessBuilder addClasspath(URL location) { 88 | try { 89 | return addClasspath(new File(location.toURI())); 90 | } catch (URISyntaxException e) { 91 | throw new IllegalArgumentException(location.toString()); 92 | } 93 | } 94 | 95 | public final JavaProcessBuilder addClasspath(File location) { 96 | if (!this.classpath.contains(location)) { 97 | this.classpath.add(location); 98 | } 99 | return this; 100 | } 101 | 102 | public final JavaProcessBuilder addClasspath(Class enclosingJar) { 103 | return addClasspath(enclosingJar.getProtectionDomain().getCodeSource().getLocation()); 104 | } 105 | 106 | public final JavaProcessBuilder arguments(List args) { 107 | this.arguments = args; 108 | return this; 109 | } 110 | 111 | public final JavaProcessBuilder arguments(String... args) { 112 | this.arguments = Arrays.asList(args); 113 | return this; 114 | } 115 | 116 | public final JavaProcessBuilder addArgument(String arg) { 117 | this.arguments.add(arg); 118 | return this; 119 | } 120 | 121 | protected void buildCommand() { 122 | if (mainClass == null) { 123 | throw new IllegalArgumentException("Missing main class"); 124 | } 125 | if (javaHome == null || !javaHome.isDirectory()) { 126 | throw new IllegalArgumentException("Bad JAVA_HOME: " + javaHome); 127 | } 128 | if (javaExecutable == null || !javaExecutable.isFile()) { 129 | throw new IllegalArgumentException("Bad Java executable: " + javaExecutable); 130 | } 131 | 132 | command.add(javaExecutable.getAbsolutePath()); 133 | command.addAll(jvmArgs); 134 | 135 | if (!classpath.isEmpty()) { 136 | for (File path : classpath) { 137 | if (!path.exists()) { 138 | throw new IllegalArgumentException("Classpath entry does not exist: " + path); 139 | } 140 | } 141 | command.add("-classpath"); 142 | StringBuilder cp = new StringBuilder(classpath.get(0).getAbsolutePath()); 143 | String sep = AnyProcess.isWindows() ? ";" : ":"; 144 | for (int i = 1; i < classpath.size(); i++) { 145 | cp.append(sep).append(classpath.get(i)); 146 | } 147 | command.add(cp.toString()); 148 | } 149 | 150 | for (Map.Entry entry : jvmProps.entrySet()) { 151 | command.add("-D" + entry.getKey() + "=" + entry.getValue()); 152 | } 153 | 154 | command.add(mainClass); 155 | command.addAll(arguments); 156 | } 157 | 158 | @Override 159 | public T build() { 160 | buildCommand(); 161 | return (T) new JavaProcess( 162 | createProcess(), 163 | pipeStdout, pipeStderr, pipeStdin, recordStdout, recordStderr, command, workingDir, 164 | javaHome, javaExecutable, jvmArgs, classpath, mainClass, arguments, jvmProps); 165 | } 166 | 167 | private static File findJavaExecutable(File javaHome) { 168 | File javaBin = new File(javaHome, "bin"); 169 | File javaPlain = new File(javaBin, "java"); 170 | File javaExe = new File(javaBin, "java.exe"); 171 | if (javaPlain.isFile()) return javaPlain; 172 | if (javaExe.isFile()) return javaExe; 173 | return null; 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/event/AbstractEventBusTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.event; 19 | 20 | import org.junit.After; 21 | import org.junit.Test; 22 | 23 | import java.util.concurrent.atomic.AtomicInteger; 24 | 25 | import static org.junit.Assert.assertEquals; 26 | import static org.junit.Assert.assertNotNull; 27 | import static org.junit.Assert.assertNull; 28 | import static org.junit.Assert.assertTrue; 29 | 30 | /** 31 | * @author Mathieu Carbou 32 | */ 33 | public abstract class AbstractEventBusTest { 34 | 35 | RecordingErrorListener errorListener = new RecordingErrorListener(); 36 | T eventBus; 37 | 38 | @After 39 | public void checkErrors() throws Throwable { 40 | if (errorListener.errors > 0) { 41 | throw errorListener.exceptions.get(0); 42 | } 43 | } 44 | 45 | @Test 46 | public void bus_has_id() { 47 | assertNotNull(eventBus.getId()); 48 | } 49 | 50 | @Test 51 | public void bus_has_toString() { 52 | assertTrue(eventBus.toString().startsWith("EventBus")); 53 | assertTrue(eventBus.toString().endsWith(":" + eventBus.getId())); 54 | } 55 | 56 | @Test 57 | public void listen_on_event() { 58 | 59 | final AtomicInteger listened = new AtomicInteger(); 60 | 61 | eventBus.on("action", new EventListener() { 62 | @Override 63 | public void onEvent(Event e) { 64 | assertEquals(eventBus.getId(), e.getSource()); 65 | assertEquals("action", e.getName()); 66 | assertEquals("data", e.getData()); 67 | assertEquals("data", e.getData(String.class)); 68 | assertTrue(System.currentTimeMillis() - 1000 < e.getTimestamp()); 69 | assertTrue(System.currentTimeMillis() >= e.getTimestamp()); 70 | assertEquals("Event{name='action', source=" + e.getSource() + ", data=data}", e.toString()); 71 | listened.incrementAndGet(); 72 | } 73 | }); 74 | 75 | eventBus.trigger("action", "data"); 76 | 77 | assertEquals(1, listened.get()); 78 | } 79 | 80 | @Test 81 | public void listen_on_all_event() { 82 | 83 | final AtomicInteger listened = new AtomicInteger(); 84 | 85 | eventBus.on(new EventListener() { 86 | @Override 87 | public void onEvent(Event e) { 88 | if (e.isUserEvent()) { 89 | assertEquals(eventBus.getId(), e.getSource()); 90 | assertEquals("data", e.getData()); 91 | assertEquals("data", e.getData(String.class)); 92 | listened.incrementAndGet(); 93 | } 94 | } 95 | }); 96 | 97 | eventBus.trigger("action1", "data"); 98 | eventBus.trigger("action2", "data"); 99 | 100 | assertEquals(2, listened.get()); 101 | } 102 | 103 | @Test 104 | public void trigger_no_data() { 105 | 106 | final AtomicInteger listened = new AtomicInteger(); 107 | 108 | eventBus.on(new EventListener() { 109 | @Override 110 | public void onEvent(Event e) { 111 | if (e.isUserEvent()) { 112 | assertEquals(eventBus.getId(), e.getSource()); 113 | assertNull(e.getData()); 114 | listened.incrementAndGet(); 115 | } 116 | } 117 | }); 118 | 119 | eventBus.trigger("action1"); 120 | 121 | assertEquals(1, listened.get()); 122 | } 123 | 124 | @Test 125 | public void unbind_listener() { 126 | RecordingEventListener listener = new RecordingEventListener(); 127 | eventBus.on(listener); 128 | eventBus.trigger("action1"); 129 | assertEquals(1, listener.events); 130 | eventBus.unbind(listener); 131 | eventBus.trigger("action1"); 132 | assertEquals(1, listener.events); 133 | } 134 | 135 | @Test 136 | public void unbind_listener_from_event() { 137 | RecordingEventListener listener = new RecordingEventListener(); 138 | eventBus.on(listener); 139 | eventBus.on("action1", listener); 140 | eventBus.trigger("action1"); 141 | assertEquals(2, listener.events); 142 | eventBus.unbind("action1", listener); 143 | eventBus.trigger("action1"); 144 | assertEquals(3, listener.events); 145 | } 146 | 147 | @Test 148 | public void unbind_from_event() { 149 | RecordingEventListener listener = new RecordingEventListener(); 150 | eventBus.on("action1", listener); 151 | eventBus.trigger("action1"); 152 | assertEquals(1, listener.events); 153 | eventBus.unbind("action1"); 154 | eventBus.trigger("action1"); 155 | assertEquals(1, listener.events); 156 | } 157 | 158 | @Test(expected = NullPointerException.class) 159 | public void error_trigger_null_event_name() { 160 | eventBus.trigger(null); 161 | } 162 | 163 | @Test(expected = UnsupportedOperationException.class) 164 | public void listener_error_rethrown_by_default_for_wildcard_listeners() { 165 | EventBus eb = new EventBus.Builder().build(); 166 | eb.on(new EventListener() { 167 | @Override 168 | public void onEvent(Event e) { 169 | throw new UnsupportedOperationException(); 170 | } 171 | }); 172 | eb.trigger("event"); 173 | } 174 | 175 | @Test(expected = UnsupportedOperationException.class) 176 | public void listener_error_rethrown_by_default_for_event_listeners() { 177 | EventBus eb = new EventBus.Builder().build(); 178 | eb.on("event", new EventListener() { 179 | @Override 180 | public void onEvent(Event e) { 181 | throw new UnsupportedOperationException(); 182 | } 183 | }); 184 | eb.trigger("event"); 185 | } 186 | 187 | @Test(expected = IllegalArgumentException.class) 188 | public void error_trigger_empty_event_name() { 189 | eventBus.trigger(""); 190 | } 191 | 192 | @Test(expected = IllegalArgumentException.class) 193 | public void error_trigger_internal_event_name() { 194 | eventBus.trigger("eventbus.something"); 195 | } 196 | 197 | @Test(expected = NullPointerException.class) 198 | public void error_on_null_event_name() { 199 | eventBus.on(null, new EventListenerAdapter()); 200 | } 201 | 202 | @Test(expected = IllegalArgumentException.class) 203 | public void error_on_empty_event_name() { 204 | eventBus.on("", new EventListenerAdapter()); 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/proc/EventJavaProcessTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import com.jayway.awaitility.Awaitility; 21 | import org.junit.Ignore; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.junit.runners.JUnit4; 25 | import org.terracotta.ipceventbus.ThreadUtil; 26 | import org.terracotta.ipceventbus.event.Event; 27 | import org.terracotta.ipceventbus.event.EventListener; 28 | import org.terracotta.ipceventbus.proc.draft.SocketClient; 29 | import org.terracotta.ipceventbus.proc.draft.SocketServer; 30 | 31 | import java.io.File; 32 | import java.io.IOException; 33 | import java.net.ServerSocket; 34 | import java.util.Arrays; 35 | import java.util.concurrent.Callable; 36 | import java.util.concurrent.CountDownLatch; 37 | import java.util.concurrent.TimeUnit; 38 | 39 | import static org.junit.Assert.assertEquals; 40 | import static org.junit.Assert.assertFalse; 41 | import static org.junit.Assert.assertTrue; 42 | 43 | /** 44 | * @author Mathieu Carbou 45 | */ 46 | @RunWith(JUnit4.class) 47 | public class EventJavaProcessTest { 48 | 49 | @Test 50 | public void test_launch_failing_ipc_process() throws InterruptedException { 51 | EventJavaProcess process = EventJavaProcess.newBuilder() 52 | .randomPort() 53 | .mainClass(EchoEvent.class.getName()) 54 | .pipeStdout() 55 | .pipeStderr() 56 | .recordStdout() 57 | .recordStderr() 58 | .build(); 59 | 60 | assertFalse(process.isEventBusConnected()); 61 | 62 | assertEquals(1, process.waitFor()); 63 | assertEquals("", process.getRecordedStdoutText()); 64 | assertTrue(process.getRecordedStderrText().contains("java.lang.ClassNotFoundException: org.terracotta.ipceventbus.proc.EchoEvent")); 65 | } 66 | 67 | @Test(timeout = 10_000) 68 | public void child_server_socket() throws Throwable { 69 | 70 | final int[] ports = getRandomPorts(1); 71 | System.out.println("ports: " + Arrays.toString(ports)); 72 | 73 | Thread serverThread = new Thread("server") { 74 | @Override 75 | public void run() { 76 | try { 77 | SocketServer.main(String.valueOf(ports[0])); 78 | } catch (Exception e) { 79 | e.printStackTrace(); 80 | } 81 | } 82 | }; 83 | serverThread.start(); 84 | 85 | Thread clientThread = new Thread("client") { 86 | @Override 87 | public void run() { 88 | try { 89 | ThreadUtil.minimumSleep(1000); // gives time for the server socket to bind and accept 90 | SocketClient.main(String.valueOf(ports[0])); 91 | } catch (Exception e) { 92 | e.printStackTrace(); 93 | } 94 | } 95 | }; 96 | clientThread.start(); 97 | 98 | serverThread.join(); 99 | clientThread.join(); 100 | 101 | String cp = new File(SocketServer.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getAbsolutePath(); 102 | final int[] ports2 = getRandomPorts(1); 103 | System.out.println("ports: " + Arrays.toString(ports)); 104 | 105 | AnyProcess serverProc = AnyProcess.newBuilder() 106 | .debug() 107 | .command("java", "-classpath", cp, SocketServer.class.getName(), String.valueOf(ports2[0])) 108 | .pipeStdout() 109 | .pipeStderr() 110 | .build(); 111 | 112 | ThreadUtil.minimumSleep(1000); // gives time for the server socket to bind and accept 113 | AnyProcess clientProc = AnyProcess.newBuilder() 114 | .debug() 115 | .command("java", "-classpath", cp, SocketClient.class.getName(), String.valueOf(ports2[0])) 116 | .pipeStdout() 117 | .pipeStderr() 118 | .build(); 119 | 120 | serverProc.waitFor(); 121 | clientProc.waitFor(); 122 | 123 | final int[] ports3 = getRandomPorts(1); 124 | System.out.println("ports: " + Arrays.toString(ports)); 125 | 126 | JavaProcess serverJava = JavaProcess.newBuilder() 127 | .mainClass(SocketServer.class) 128 | .addClasspath(SocketServer.class) 129 | .arguments(String.valueOf(ports3[0])) 130 | .pipeStdout() 131 | .pipeStderr() 132 | .debug() 133 | .build(); 134 | 135 | ThreadUtil.minimumSleep(1000); // gives time for the server socket to bind and accept 136 | JavaProcess clientJava = JavaProcess.newBuilder() 137 | .mainClass(SocketClient.class) 138 | .addClasspath(SocketClient.class) 139 | .arguments(String.valueOf(ports3[0])) 140 | .pipeStdout() 141 | .pipeStderr() 142 | .debug() 143 | .build(); 144 | 145 | serverJava.waitFor(); 146 | clientJava.waitFor(); 147 | } 148 | 149 | @Test(timeout = 10_000) 150 | public void sample_usage_launch_ipc_process() throws Throwable { 151 | 152 | EventJavaProcess process = EventJavaProcess.newBuilder() 153 | .randomPort() 154 | .mainClass(EchoEvent.class.getName()) 155 | .addClasspath(EchoEvent.class) 156 | .arguments("one", "two") 157 | .recordStdout() 158 | .recordStderr() 159 | .pipeStdout() 160 | .pipeStderr() 161 | .debug() 162 | .build(); 163 | 164 | System.out.println("Test: PID: " + process.getCurrentPid()); 165 | System.out.println("Test: CHILD PID: " + process.getPid()); 166 | 167 | Awaitility.waitAtMost(5, TimeUnit.SECONDS).until(new Callable() { 168 | @Override 169 | public Boolean call() { 170 | return process.isEventBusConnected(); 171 | } 172 | }); 173 | 174 | process.trigger("ping", "hello"); 175 | process.trigger("process.exit"); 176 | 177 | assertEquals(0, process.waitFor()); 178 | assertEquals("", process.getRecordedStderrText()); 179 | } 180 | 181 | @Test(timeout = 10_000) 182 | public void be_alerted_of_process_end() throws Throwable { 183 | 184 | EventJavaProcess process = EventJavaProcess.newBuilder() 185 | .mainClass(EchoEvent2.class) // set main class to start and add it to classpath 186 | .pipeStdout() // echo stdout 187 | .pipeStderr() // echo stderr 188 | .debug() // activate debug mode for ipc eventbus 189 | .build(); 190 | 191 | Awaitility.waitAtMost(5, TimeUnit.SECONDS).until(new Callable() { 192 | @Override 193 | public Boolean call() { 194 | return process.isEventBusConnected(); 195 | } 196 | }); 197 | 198 | final CountDownLatch latch = new CountDownLatch(3); 199 | 200 | process.on("process.exiting", new EventListener() { 201 | @Override 202 | public void onEvent(Event e) throws Throwable { 203 | System.out.println("Exiting..."); 204 | latch.countDown(); 205 | } 206 | }); 207 | 208 | process.on("process.exited", new EventListener() { 209 | @Override 210 | public void onEvent(Event e) throws Throwable { 211 | System.out.println("Exited."); 212 | latch.countDown(); 213 | } 214 | }); 215 | 216 | process.on("pong", new EventListener() { 217 | @Override 218 | public void onEvent(Event e) throws Throwable { 219 | System.out.println(e.getData()); 220 | latch.countDown(); 221 | } 222 | }); 223 | 224 | process.trigger("ping", "hello"); 225 | process.trigger("process.exit"); 226 | 227 | process.waitFor(); 228 | 229 | latch.await(); 230 | } 231 | 232 | private static int[] getRandomPorts(int n) { 233 | int[] ports = new int[n]; 234 | for (int port = 2000, i = 0; i < ports.length && port < 65000; port++) { 235 | try { 236 | new ServerSocket(port).close(); 237 | ports[i++] = port; 238 | } catch (IOException ignored) { 239 | } 240 | } 241 | return ports; 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/proc/AnyProcessTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.JUnit4; 23 | import org.terracotta.ipceventbus.ThreadUtil; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.io.PipedInputStream; 28 | import java.io.PipedOutputStream; 29 | import java.util.Arrays; 30 | import java.util.concurrent.CancellationException; 31 | import java.util.concurrent.ExecutionException; 32 | import java.util.concurrent.TimeUnit; 33 | import java.util.concurrent.TimeoutException; 34 | 35 | import static org.hamcrest.MatcherAssert.assertThat; 36 | import static org.hamcrest.Matchers.greaterThan; 37 | import static org.junit.Assert.assertEquals; 38 | import static org.junit.Assert.assertFalse; 39 | import static org.junit.Assert.assertTrue; 40 | import static org.junit.Assert.fail; 41 | 42 | /** 43 | * @author Mathieu Carbou 44 | */ 45 | @RunWith(JUnit4.class) 46 | public class AnyProcessTest { 47 | 48 | @Test 49 | public void test_launch_process() throws InterruptedException { 50 | AnyProcess anyProcess = AnyProcess.newBuilder() 51 | .command("bash", "-c", "sleep 5; echo $VAR") 52 | .env("VAR", "Hello world!") 53 | .pipeStdout() 54 | .pipeStderr() 55 | .recordStdout() 56 | .recordStderr() 57 | .build(); 58 | 59 | try { 60 | anyProcess.exitValue(); 61 | fail(); 62 | } catch (Exception e) { 63 | assertEquals(IllegalThreadStateException.class, e.getClass()); 64 | } 65 | 66 | try { 67 | anyProcess.getRecordedStdout(); 68 | fail(); 69 | } catch (Exception e) { 70 | assertEquals(IllegalThreadStateException.class, e.getClass()); 71 | assertEquals("Process not terminated.", e.getMessage()); 72 | } 73 | 74 | try { 75 | anyProcess.getRecordedStderr(); 76 | fail(); 77 | } catch (Exception e) { 78 | assertEquals(IllegalThreadStateException.class, e.getClass()); 79 | assertEquals("Process not terminated.", e.getMessage()); 80 | } 81 | 82 | try { 83 | anyProcess.getRecordedStdoutText(); 84 | fail(); 85 | } catch (Exception e) { 86 | assertEquals(IllegalThreadStateException.class, e.getClass()); 87 | assertEquals("Process not terminated.", e.getMessage()); 88 | } 89 | 90 | try { 91 | anyProcess.getRecordedStderrText(); 92 | fail(); 93 | } catch (Exception e) { 94 | assertEquals(IllegalThreadStateException.class, e.getClass()); 95 | assertEquals("Process not terminated.", e.getMessage()); 96 | } 97 | 98 | assertThat(anyProcess.getPid(), greaterThan(0L)); 99 | assertTrue(anyProcess.isRunning()); 100 | assertEquals(Arrays.asList("bash", "-c", "sleep 5; echo $VAR"), anyProcess.getCommand()); 101 | assertEquals(new File("."), anyProcess.getWorkingDirectory()); 102 | assertEquals(0, anyProcess.waitFor()); 103 | assertEquals(0, anyProcess.exitValue()); 104 | assertEquals("", anyProcess.getRecordedStderrText()); 105 | assertEquals("Hello world!\n", anyProcess.getRecordedStdoutText()); 106 | } 107 | 108 | @Test 109 | public void test_launch_process_without_collecting() throws InterruptedException { 110 | AnyProcess anyProcess = AnyProcess.newBuilder() 111 | .command("bash", "-c", "sleep 2; echo $VAR") 112 | .env("VAR", "Hello world!") 113 | .build(); 114 | 115 | try { 116 | anyProcess.getRecordedStdout(); 117 | fail(); 118 | } catch (Exception e) { 119 | assertEquals(IllegalThreadStateException.class, e.getClass()); 120 | assertEquals("Stdout not recorded.", e.getMessage()); 121 | } 122 | 123 | try { 124 | anyProcess.getRecordedStderr(); 125 | fail(); 126 | } catch (Exception e) { 127 | assertEquals(IllegalThreadStateException.class, e.getClass()); 128 | assertEquals("Stderr not recorded.", e.getMessage()); 129 | } 130 | 131 | try { 132 | anyProcess.getRecordedStdoutText(); 133 | fail(); 134 | } catch (Exception e) { 135 | assertEquals(IllegalThreadStateException.class, e.getClass()); 136 | assertEquals("Stdout not recorded.", e.getMessage()); 137 | } 138 | 139 | try { 140 | anyProcess.getRecordedStderrText(); 141 | fail(); 142 | } catch (Exception e) { 143 | assertEquals(IllegalThreadStateException.class, e.getClass()); 144 | assertEquals("Stderr not recorded.", e.getMessage()); 145 | } 146 | 147 | assertEquals(0, anyProcess.waitFor()); 148 | } 149 | 150 | @Test(timeout = 10_000) 151 | public void test_destroy() throws InterruptedException { 152 | AnyProcess proc = AnyProcess.newBuilder() 153 | .command("bash", "-c", "sleep 3; echo $VAR") 154 | .pipeStdout() 155 | .pipeStderr() 156 | .env("VAR", "Hello world!") 157 | .build(); 158 | 159 | ThreadUtil.minimumSleep(500); 160 | proc.destroy(); 161 | assertTrue(proc.isDestroyed()); 162 | 163 | // 143 = return code when SIGKILL 164 | assertEquals(143, proc.exitValue()); 165 | assertEquals(143, proc.waitFor()); 166 | } 167 | 168 | @Test 169 | public void using_future() throws InterruptedException, TimeoutException, ExecutionException { 170 | AnyProcess proc = AnyProcess.newBuilder() 171 | .command("bash", "-c", "sleep 2; echo $VAR") 172 | .pipeStdout() 173 | .pipeStderr() 174 | .env("VAR", "Hello world!") 175 | .build(); 176 | 177 | assertEquals(0, proc.getFuture().get(3, TimeUnit.SECONDS).intValue()); 178 | assertTrue(proc.getFuture().isDone()); 179 | assertFalse(proc.getFuture().isCancelled()); 180 | assertEquals(0, proc.getFuture().get().intValue()); 181 | } 182 | 183 | @Test(timeout = 10_000) 184 | public void test_destroy_future() throws InterruptedException { 185 | AnyProcess proc = AnyProcess.newBuilder() 186 | .command("bash", "-c", "sleep 3; echo $VAR") 187 | .pipeStdout() 188 | .pipeStderr() 189 | .env("VAR", "Hello world!") 190 | .build(); 191 | 192 | ThreadUtil.minimumSleep(500); 193 | boolean wasCancelled = proc.getFuture().cancel(true); 194 | assertTrue(proc.isDestroyed()); 195 | 196 | assertTrue(proc.getFuture().isDone()); 197 | assertTrue(proc.getFuture().isCancelled() == wasCancelled); 198 | 199 | // 143 = return code when SIGKILL 200 | assertEquals(143, proc.exitValue()); 201 | assertEquals(143, proc.waitFor()); 202 | 203 | try { 204 | proc.getFuture().get(); 205 | if (wasCancelled) { 206 | fail(); 207 | } 208 | } catch (Exception e) { 209 | assertEquals(CancellationException.class, e.getClass()); 210 | } 211 | } 212 | 213 | @Test 214 | public void test_waitForTime() throws InterruptedException, TimeoutException { 215 | AnyProcess proc = AnyProcess.newBuilder() 216 | .command("bash", "-c", "sleep 3; echo $VAR") 217 | .pipeStdout() 218 | .pipeStderr() 219 | .env("VAR", "Hello world!") 220 | .build(); 221 | 222 | try { 223 | proc.waitForTime(1, TimeUnit.SECONDS); 224 | fail(); 225 | } catch (TimeoutException ignored) { 226 | } 227 | 228 | assertEquals(0, proc.waitForTime(3, TimeUnit.SECONDS)); 229 | } 230 | 231 | @Test 232 | public void pipe_stdin() throws InterruptedException, TimeoutException, ExecutionException, IOException { 233 | PipedInputStream in = new PipedInputStream(); 234 | PipedOutputStream out = new PipedOutputStream(in); 235 | AnyProcess proc = AnyProcess.newBuilder() 236 | .command("bash", "-c", "read input && echo $input") 237 | .pipeStdout() 238 | .pipeStderr() 239 | .pipeStdin(in) 240 | .recordStderr() 241 | .recordStdout() 242 | .build(); 243 | out.write("Hello World!\n".getBytes()); 244 | out.close(); 245 | assertEquals(0, proc.waitFor()); 246 | assertEquals("Hello World!\n", proc.getRecordedStdoutText()); 247 | } 248 | 249 | } 250 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 4.0.0 21 | 22 | org.terracotta 23 | ipc-eventbus 24 | 1.1.6-SNAPSHOT 25 | jar 26 | 27 | IPC EventBus 28 | A library that offers a local and remote event bus system and process launching capabilities, with inter-process communication 29 | https://github.com/Terracotta-OSS/ipc-eventbus/ 30 | 31 | 32 | 33 | The Apache License, Version 2.0 34 | http://www.apache.org/licenses/LICENSE-2.0.txt 35 | 36 | 37 | 38 | 39 | UTF-8 40 | UTF-8 41 | 1.8 42 | http://nexus.terracotta.eur.ad.sag/content/repositories/terracotta-os-snapshots 43 | http://nexus.terracotta.eur.ad.sag/content/repositories/terracotta-os-releases 44 | false 45 | 46 | 47 | 48 | 49 | net.java.dev.jna 50 | jna-platform 51 | 4.1.0 52 | true 53 | 54 | 55 | 56 | com.google.code.findbugs 57 | annotations 58 | 3.0.0 59 | true 60 | 61 | 62 | junit 63 | junit 64 | 4.12 65 | test 66 | 67 | 68 | com.jayway.awaitility 69 | awaitility 70 | 1.3.4 71 | test 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.sonatype.plugins 80 | nexus-staging-maven-plugin 81 | 1.7.0 82 | true 83 | 84 | 85 | https://ossrh-staging-api.central.sonatype.com/ 86 | 87 | ossrh 88 | ${maven.deploy.skip} 89 | 90 | 91 | 92 | 93 | 94 | 95 | maven-compiler-plugin 96 | 3.8.1 97 | 98 | ${java.version} 99 | ${java.version} 100 | 101 | 102 | 103 | maven-source-plugin 104 | 3.1.0 105 | 106 | 107 | verify 108 | 109 | jar-no-fork 110 | 111 | 112 | 113 | 114 | 115 | org.codehaus.mojo 116 | findbugs-maven-plugin 117 | 3.0.5 118 | 119 | 120 | 121 | check 122 | 123 | 124 | 125 | 126 | 127 | org.apache.rat 128 | apache-rat-plugin 129 | 0.11 130 | 131 | 132 | verify 133 | 134 | check 135 | 136 | 137 | 138 | 139 | 140 | nb-configuration.xml 141 | 142 | 143 | 144 | 145 | org.apache.maven.plugins 146 | maven-javadoc-plugin 147 | 3.1.1 148 | 149 | ${skipJavadoc} 150 | 8 151 | 152 | 153 | 154 | attach-javadocs 155 | 156 | jar 157 | 158 | 159 | 160 | 161 | 162 | org.sonatype.plugins 163 | nexus-staging-maven-plugin 164 | 165 | 166 | 167 | 168 | 169 | 170 | deploy-sonatype 171 | 172 | 173 | sonatype-nexus-staging 174 | https://oss.sonatype.org/service/local/staging/deploy/maven2 175 | 176 | 177 | 178 | 179 | 180 | sign-artifacts 181 | 182 | 183 | performRelease 184 | true 185 | 186 | 187 | 188 | 189 | 190 | org.apache.maven.plugins 191 | maven-gpg-plugin 192 | 1.6 193 | 194 | 195 | sign-artifacts 196 | verify 197 | 198 | sign 199 | 200 | 201 | Terracotta Release Engineer 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | terracotta-os-releases 214 | Terracotta OS Releases Repository 215 | ${terracotta-os-releases-url} 216 | 217 | 218 | terracotta-os-snapshots 219 | false 220 | Terracotta OS Snapshots Repository 221 | ${terracotta-os-snapshots-url} 222 | 223 | 224 | 225 | 226 | scm:git:https://github.com/terracotta-oss/ipc-eventbus.git 227 | scm:git:git@github.com:terracotta-oss/ipc-eventbus.git 228 | https://github.com/terracotta-oss/ipc-eventbus 229 | 230 | 231 | 232 | 233 | Mathieu Carbou 234 | Mathieu.Carbou@ibm.com 235 | IBM 236 | https://terracotta.org 237 | 238 | 239 | 240 | -------------------------------------------------------------------------------- /src/test/java/org/terracotta/ipceventbus/proc/draft/Doc.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc.draft; 19 | 20 | import org.terracotta.ipceventbus.event.Event; 21 | import org.terracotta.ipceventbus.event.EventBus; 22 | import org.terracotta.ipceventbus.event.EventBusClient; 23 | import org.terracotta.ipceventbus.event.EventBusServer; 24 | import org.terracotta.ipceventbus.event.EventListener; 25 | import org.terracotta.ipceventbus.event.EventListenerSniffer; 26 | import org.terracotta.ipceventbus.event.PrintingErrorListener; 27 | import org.terracotta.ipceventbus.event.RethrowingErrorListener; 28 | import org.terracotta.ipceventbus.io.MultiplexOutputStream; 29 | import org.terracotta.ipceventbus.io.Pipe; 30 | import org.terracotta.ipceventbus.proc.AnyProcess; 31 | import org.terracotta.ipceventbus.proc.Echo; 32 | import org.terracotta.ipceventbus.proc.EventJavaProcess; 33 | import org.terracotta.ipceventbus.proc.JavaProcess; 34 | 35 | import java.io.File; 36 | import java.io.IOException; 37 | import java.io.InputStream; 38 | import java.io.OutputStream; 39 | import java.util.HashMap; 40 | import java.util.concurrent.TimeUnit; 41 | import java.util.concurrent.TimeoutException; 42 | 43 | /** 44 | * @author Mathieu Carbou 45 | */ 46 | public class Doc { 47 | public static void main(String[] args) throws InterruptedException, IOException, TimeoutException { 48 | InputStream inputStream = null; 49 | OutputStream outputStream = null; 50 | OutputStream outputStream2 = null; 51 | byte[] data = null; 52 | Event event = null; 53 | 54 | Pipe pipe = new Pipe("collect stdout", inputStream, outputStream); 55 | pipe.waitFor(); // if you need, you can wait for the pipe to finish 56 | pipe.close(); // close the pipe (does not close the stream!) 57 | 58 | MultiplexOutputStream plex = new MultiplexOutputStream() 59 | .addOutputStream(outputStream) 60 | .addOutputStream(outputStream2) 61 | .addOutputStream(System.out); 62 | 63 | plex.getOutputStreams(); // lists the streams 64 | plex.isEmpty(); // true if no streams 65 | plex.streamCount(); // number of streams 66 | plex.write(data); // MultiplexOutputStream is an OutputStream 67 | 68 | EventBus eventBus = new EventBus.Builder() 69 | .onError(new PrintingErrorListener(System.err)) // OPTIONAL: print listener exceptions 70 | .onError(new RethrowingErrorListener()) // OPTIONAL: rethrow listener exceptions immediately (by default) 71 | .id("bus-id") // OPTIONAL: bus id (otherwise a UUID is generated) 72 | .build(); 73 | 74 | eventBus.getId(); // returns the eventbus id 75 | 76 | EventListener listener = new EventListener() { 77 | @Override 78 | public void onEvent(Event e) { 79 | } 80 | }; 81 | 82 | eventBus.on(listener); // register a listener for all events 83 | eventBus.on("my.event", listener); // register a listener for a specific event 84 | 85 | eventBus.on(new EventListenerSniffer()); // listener which dumps to the console all events (for debug) 86 | 87 | eventBus.unbind(listener); // removes a listener from all events 88 | eventBus.unbind("my.event"); // removes all listeners for this event 89 | eventBus.unbind("my.event", listener); // removes a specific listener from an event 90 | 91 | eventBus.trigger("my.event"); // can trigger an event 92 | eventBus.trigger("my.event", "some data"); // event with data (must be serializable) 93 | 94 | // Listener will receive an event: 95 | event.getData(); // the data 96 | event.getData(String.class); // can cast the data in the wanted type 97 | event.getName(); // the name of the event triggered 98 | event.getSource(); // the ID of the eventbus 99 | event.getTimestamp(); // time in ms of the event 100 | event.isUserEvent(); // check if it is a user event. You might listen to system events such as eventbus.server.close, eventbus.client.connect, eventbus.client.disconnect 101 | 102 | EventBusServer server = new EventBusServer.Builder() 103 | .id("peer1") // OPTIONAL: bus id 104 | .bind("0.0.0.0") // OPTIONAL: bind address 105 | .listen(56789) // OPTIONAL: port to listen to. Default to 56789 106 | .listenRandom() // OPTIONAL: choose a random port for listening 107 | .build(); 108 | 109 | EventBusClient client = new EventBusClient.Builder() 110 | .id("peer2") 111 | .connect(56789) // OPTIONAL: port to connect to 112 | .connect("lcoalhost", 56789) // OPTIONAL: port and host to connect to. Default is localhost:56789 113 | .build(); 114 | 115 | AnyProcess process = AnyProcess.newBuilder() 116 | .command("bash", "-c", "sleep 3; echo $VAR") 117 | .recordStdout() // OPTIONAL: save stdout from process for getStdout() (disabled by default). Disables getInputStream(). 118 | .recordStderr() // OPTIONAL: save stderr from process for getStderr() (disabled by default). Disabled getErrorStream(). 119 | .env("key", "value") // OPTIONAL: add a env. variable 120 | .env(new HashMap()) // OPTIONAL: se ta new env 121 | .pipeStderr() // OPTIONAL: send stderr to the console 122 | .pipeStderr(outputStream) // OPTIONAL: send stderr to a stream. You can both collect and pipe. 123 | .pipeStdout() // OPTIONAL: send stdout to the console 124 | .pipeStdout(outputStream) // OPTIONAL: send stdout to a stream. You can both collect and pipe. 125 | .pipeStdin() // OPTIONAL: will bind process stdin to this process stding 126 | .pipeStdin(inputStream) // OPTIONAL: will bind process stdin to this input stream 127 | .redirectStderr() // OPTIONAL: treat stderr like stdout (both merged into stdout) 128 | .workingDir(new File(".")) // OPTIONAL: change the working directly. Same as current process by default 129 | .build(); 130 | 131 | process.destroy(); // destroy (kill with SIGTERM) the process 132 | process.exitValue(); // the process exit value, when available 133 | process.getCommand(); // the process command 134 | process.getErrorStream(); 135 | process.getFuture(); // get a future representing the process execution. You can cancel (=destroy) the process or wait for its completion 136 | process.getInputStream(); 137 | process.getOutputStream(); 138 | process.getPid(); // get the process PID 139 | process.getRecordedStderr(); // if collected, get the stderr of the process 140 | process.getRecordedStderrText(); // if collected, get the stderr of the process as a String 141 | process.getRecordedStdout(); // if collected, get the stdout of the process 142 | process.getRecordedStdoutText(); // if collected, get the stdout of the process as a String 143 | process.getWorkingDirectory(); 144 | process.isDestroyed(); // check if process is destroyed 145 | process.isRunning(); // check if process is still running 146 | process.waitFor(); // wait and block while process finished 147 | process.waitForTime(1, TimeUnit.MINUTES); // wait for the process to finish or timeout 148 | 149 | JavaProcess javaProcess = JavaProcess.newBuilder() 150 | .mainClass("my.company.Echo") 151 | .addClasspath(Echo.class) // add a classpath entry from a class (find the enclosing jar or folder) 152 | .arguments("one", "two") // add some program arguments 153 | .addJvmProp("my.prop", "world") // add some jvm props 154 | .addJvmArg("-Xmx512m") // add some jvm flags 155 | .env("VAR", "Hello") // add some env. variable 156 | .pipeStdout() // you can access all process builder methods seen above 157 | .pipeStderr() 158 | .recordStdout() 159 | .recordStderr() 160 | .build(); 161 | 162 | javaProcess.getJavaExecutable(); // automatically resolved from java home, but you can override it in the builder 163 | javaProcess.getJavaHome(); // automatically resolved from java home, but you can override it in the builder 164 | 165 | EventJavaProcess ipc = EventJavaProcess.newBuilder() 166 | .port(1234) 167 | .mainClass("my.corp.Echo") 168 | .arguments("one", "two") 169 | .build(); 170 | 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/main/java/org/terracotta/ipceventbus/proc/AnyProcess.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Terracotta, Inc., a Software AG company. 3 | * Copyright IBM Corp. 2024, 2025 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.terracotta.ipceventbus.proc; 19 | 20 | import java.lang.reflect.InvocationTargetException; 21 | import java.lang.reflect.Method; 22 | import org.terracotta.ipceventbus.io.MultiplexOutputStream; 23 | import org.terracotta.ipceventbus.io.Pipe; 24 | 25 | import java.io.ByteArrayOutputStream; 26 | import java.io.File; 27 | import java.io.InputStream; 28 | import java.io.OutputStream; 29 | import java.lang.management.ManagementFactory; 30 | import java.lang.reflect.Field; 31 | import java.nio.charset.Charset; 32 | import java.nio.charset.StandardCharsets; 33 | import java.util.ArrayList; 34 | import java.util.Collection; 35 | import java.util.Collections; 36 | import java.util.List; 37 | import java.util.concurrent.ExecutionException; 38 | import java.util.concurrent.Future; 39 | import java.util.concurrent.FutureTask; 40 | import java.util.concurrent.TimeUnit; 41 | import java.util.concurrent.TimeoutException; 42 | 43 | import static java.lang.Class.forName; 44 | 45 | /** 46 | * @author Mathieu Carbou 47 | */ 48 | public class AnyProcess extends Process { 49 | 50 | private static final Charset UTF8 = StandardCharsets.UTF_8; 51 | 52 | private final long pid; 53 | private final Process process; 54 | private volatile boolean running = true; 55 | private volatile boolean destroyed; 56 | private final FutureTask future; 57 | private final Collection pipes = new ArrayList<>(3); 58 | private final ByteArrayOutputStream recordedStdout; 59 | private final ByteArrayOutputStream recordedStderr; 60 | private final List command; 61 | private final File workingDirectory; 62 | private boolean stdoutStreamDisabled; 63 | private boolean stderrStreamDisabled; 64 | 65 | AnyProcess(Process process, OutputStream pipeStdout, OutputStream pipeStderr, InputStream pipeStdin, boolean collectStdout, boolean collectStderr, List command, File workingDirectory) { 66 | this.process = process; 67 | this.pid = getPid(process); 68 | this.command = Collections.unmodifiableList(command); 69 | this.workingDirectory = workingDirectory; 70 | 71 | if (pipeStdin != null) { 72 | this.pipes.add(new Pipe("Process Pipe stdin@" + this.pid, pipeStdin, getOutputStream())); 73 | } 74 | 75 | // multiplex process stdout 76 | { 77 | MultiplexOutputStream stdoutPlex = new MultiplexOutputStream(); 78 | if (pipeStdout != null) { 79 | stdoutPlex.addOutputStream(pipeStdout); 80 | } 81 | if (collectStdout) { 82 | this.recordedStdout = new ByteArrayOutputStream(); 83 | stdoutPlex.addOutputStream(this.recordedStdout); 84 | } else { 85 | this.recordedStdout = null; 86 | } 87 | if (!stdoutPlex.isEmpty()) { 88 | this.pipes.add(new Pipe("Process Pipe stdout@" + this.pid, getInputStream(), stdoutPlex)); 89 | this.stdoutStreamDisabled = true; 90 | } 91 | } 92 | 93 | // multiplex process stderr 94 | { 95 | MultiplexOutputStream stderrPlex = new MultiplexOutputStream(); 96 | if (pipeStderr != null) { 97 | stderrPlex.addOutputStream(pipeStderr); 98 | } 99 | if (collectStderr) { 100 | this.recordedStderr = new ByteArrayOutputStream(); 101 | stderrPlex.addOutputStream(this.recordedStderr); 102 | } else { 103 | this.recordedStderr = null; 104 | } 105 | if (!stderrPlex.isEmpty()) { 106 | this.pipes.add(new Pipe("Process Pipe stderr@" + this.pid, getErrorStream(), stderrPlex)); 107 | this.stderrStreamDisabled = true; 108 | } 109 | } 110 | 111 | // starts future 112 | { 113 | this.future = new FutureTask(() -> { 114 | int r = AnyProcess.this.process.waitFor(); 115 | processFinished(); 116 | return r; 117 | }) { 118 | @Override 119 | public boolean cancel(boolean mayInterruptIfRunning) { 120 | if (mayInterruptIfRunning) { 121 | AnyProcess.this.process.destroy(); 122 | } 123 | boolean interrupted = false; 124 | try { 125 | AnyProcess.this.process.waitFor(); 126 | } catch (InterruptedException ignored) { 127 | interrupted = true; 128 | } 129 | try { 130 | destroyed = true; 131 | processFinished(); 132 | return super.cancel(mayInterruptIfRunning); 133 | } finally { 134 | if (interrupted) { 135 | // restore interrupt state 136 | Thread.currentThread().interrupt(); 137 | } 138 | } 139 | } 140 | }; 141 | Thread thread = new Thread(future, "Process future@" + this.pid); 142 | thread.setDaemon(true); 143 | thread.start(); 144 | } 145 | } 146 | 147 | @Override 148 | public final OutputStream getOutputStream() { 149 | if (!isRunning()) throw new IllegalStateException("Not running"); 150 | return this.process.getOutputStream(); 151 | } 152 | 153 | @Override 154 | public final InputStream getInputStream() { 155 | if (!isRunning()) throw new IllegalStateException("Not running"); 156 | if (stdoutStreamDisabled) throw new IllegalStateException("no stdout stream available"); 157 | return this.process.getInputStream(); 158 | } 159 | 160 | @Override 161 | public final InputStream getErrorStream() { 162 | if (!isRunning()) throw new IllegalStateException("Not running"); 163 | if (stderrStreamDisabled) throw new IllegalStateException("No stderr stream available"); 164 | return this.process.getErrorStream(); 165 | } 166 | 167 | @Override 168 | public final int waitFor() throws InterruptedException { 169 | if (!isRunning()) { 170 | return process.exitValue(); 171 | } 172 | try { 173 | return future.get(); 174 | } catch (ExecutionException e) { 175 | if (e.getCause() instanceof InterruptedException) { 176 | throw (InterruptedException) e.getCause(); 177 | } 178 | if (e.getCause() instanceof Error) { 179 | throw (Error) e.getCause(); 180 | } 181 | if (e.getCause() instanceof RuntimeException) { 182 | throw (RuntimeException) e.getCause(); 183 | } 184 | throw new RuntimeException(e.getCause()); 185 | } 186 | } 187 | 188 | public final int waitForTime(long time, TimeUnit unit) throws InterruptedException, TimeoutException { 189 | if (!isRunning()) { 190 | return process.exitValue(); 191 | } 192 | try { 193 | return future.get(time, unit); 194 | } catch (ExecutionException e) { 195 | if (e.getCause() instanceof InterruptedException) { 196 | throw (InterruptedException) e.getCause(); 197 | } 198 | if (e.getCause() instanceof Error) { 199 | throw (Error) e.getCause(); 200 | } 201 | if (e.getCause() instanceof RuntimeException) { 202 | throw (RuntimeException) e.getCause(); 203 | } 204 | throw new RuntimeException(e.getCause()); 205 | } 206 | } 207 | 208 | @Override 209 | public final int exitValue() { 210 | int c = process.exitValue(); 211 | // if process is not running, an exception is not thrown, thus it means the process is finished 212 | processFinished(); 213 | return c; 214 | } 215 | 216 | private void processFinished() { 217 | if (destroyed) { 218 | finishPipe(false); 219 | } else { 220 | finishPipe(true); 221 | } 222 | running = false; 223 | if (destroyed) { 224 | onDestroyed(); 225 | } else { 226 | onTerminated(); 227 | } 228 | } 229 | 230 | @Override 231 | public final void destroy() { 232 | future.cancel(true); 233 | } 234 | 235 | @Override 236 | public String toString() { 237 | return getCommandLine(); 238 | } 239 | 240 | public final List getCommand() { 241 | return command; 242 | } 243 | 244 | public final String getCommandLine() { 245 | StringBuilder sb = new StringBuilder(); 246 | sb.append(command.get(0)); 247 | for (int i = 1; i < command.size(); i++) { 248 | sb.append(" ").append(command.get(i)); 249 | } 250 | return sb.toString(); 251 | } 252 | 253 | public final File getWorkingDirectory() { 254 | return workingDirectory; 255 | } 256 | 257 | public final long getPid() { 258 | return pid; 259 | } 260 | 261 | public final boolean isRunning() { 262 | return running; 263 | } 264 | 265 | public final boolean isDestroyed() { 266 | return destroyed; 267 | } 268 | 269 | public final Future getFuture() { 270 | return future; 271 | } 272 | 273 | public final byte[] getRecordedStdout() throws IllegalThreadStateException { 274 | if (recordedStdout == null) { 275 | throw new IllegalThreadStateException("Stdout not recorded."); 276 | } 277 | if (isRunning()) { 278 | throw new IllegalThreadStateException("Process not terminated."); 279 | } 280 | return recordedStdout.toByteArray(); 281 | } 282 | 283 | public final byte[] getRecordedStderr() throws IllegalThreadStateException { 284 | if (recordedStderr == null) { 285 | throw new IllegalThreadStateException("Stderr not recorded."); 286 | } 287 | if (isRunning()) { 288 | throw new IllegalThreadStateException("Process not terminated."); 289 | } 290 | return recordedStderr.toByteArray(); 291 | } 292 | 293 | public final String getRecordedStdoutText() throws IllegalThreadStateException { 294 | return new String(getRecordedStdout(), UTF8); 295 | } 296 | 297 | public final String getRecordedStderrText() throws IllegalThreadStateException { 298 | return new String(getRecordedStderr(), UTF8); 299 | } 300 | 301 | protected void onTerminated() { 302 | } 303 | 304 | protected void onDestroyed() { 305 | } 306 | 307 | private void finishPipe(boolean wait) { 308 | synchronized (pipes) { 309 | for (Pipe pipe : pipes) { 310 | try { 311 | if (wait) { 312 | try { 313 | pipe.waitFor(); 314 | } catch (InterruptedException e) { 315 | Thread.currentThread().interrupt(); 316 | break; 317 | } 318 | } 319 | } finally { 320 | pipe.close(); 321 | } 322 | } 323 | pipes.clear(); 324 | } 325 | } 326 | 327 | public static AnyProcessBuilder newBuilder() { 328 | return new AnyProcessBuilder<>(); 329 | } 330 | 331 | protected static long getPid(Process process) { 332 | if (isWindows()) { 333 | if (isJnaAvailable()) { 334 | return Jna.getWindowsPid(process); 335 | } 336 | } else { 337 | try { 338 | Method pidMethod = forName("java.lang.Process").getMethod("pid"); 339 | return (long) pidMethod.invoke(process); 340 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) { 341 | String procClassName = process.getClass().getName(); 342 | if (procClassName.equals("java.lang.ProcessImpl") || procClassName.equals("java.lang.UNIXProcess")) { 343 | try { 344 | Field f = process.getClass().getDeclaredField("pid"); 345 | f.setAccessible(true); 346 | return f.getInt(process); 347 | } catch (Throwable ignored) { 348 | } 349 | } 350 | } 351 | } 352 | return -1; 353 | } 354 | 355 | private static boolean isJnaAvailable() { 356 | try { 357 | AnyProcess.class.getClassLoader().loadClass("com.sun.jna.platform.win32.Kernel32"); 358 | return true; 359 | } catch (ClassNotFoundException e) { 360 | return false; 361 | } 362 | } 363 | 364 | protected static boolean isWindows() { 365 | return System.getProperty("os.name", "unknown").toLowerCase().contains("windows"); 366 | } 367 | 368 | protected static final String getCurrentPid() { 369 | try { 370 | return ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; 371 | } catch (Exception ignored) { 372 | return null; 373 | } 374 | } 375 | 376 | } 377 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | //// 2 | Copyright 2015 Terracotta, Inc., a Software AG company. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | //// 16 | 17 | = IPC EventBus 18 | 19 | IPC EventBus provides a simple EventBus API for intra-JVM and extra-JVM communication. You can use it within a JVM as a simple EventBus or between JVM for communication between process. Besides, this project comes with several builders to easily start process and bind them an event bus. 20 | 21 | The goal of this project is to be: 22 | 23 | - self-contained (no external dependency) 24 | - simple 25 | - efficient 26 | - targets small usages, i.e. integration tests, system tests, which request several process synchronization and classpath isolation 27 | - Java 8 Compatible, so that you can use it in any TC project 28 | 29 | Licensed under the Apache License, Version 2.0 30 | (C) Terracotta, Inc., a Software AG company. 31 | Copyright IBM Corp. 2024, 2025 32 | 33 | http://www.terracotta.org 34 | 35 | See also: http://www.github.com/ehcache/ehcache3 36 | 37 | == Build and Maven dependency 38 | 39 | Fork, then: 40 | 41 | ``` 42 | git clone git@github.com:Terracotta-OSS/ipc-eventbus.git 43 | mvn clean install 44 | ``` 45 | 46 | Status of the build: image:https://terracotta-oss.ci.cloudbees.com/buildStatus/icon?job=ipc-eventbus[Terracotta-OSS@Cloudbees, link="https://terracotta-oss.ci.cloudbees.com/job/ipc-eventbus"] 47 | 48 | ``` 49 | 50 | org.terracotta 51 | ipc-eventbus 52 | X.Y 53 | 54 | ``` 55 | 56 | == What's Available 57 | 58 | This project contains several modules: 59 | 60 | - **I/O Stuff** 61 | 62 | An EventBus API with a local and remote implementation: 63 | 64 | - **Local EventBus** 65 | - **Remote EventBus** 66 | 67 | Process launching mechanisms: 68 | 69 | - **Process Launching** 70 | - **Java Process Launching** 71 | 72 | And a way to start a Java process which can interact with its launcher through an event bus: 73 | 74 | - [Inter Java Process Communication](#ipc) 75 | 76 | === I/O Stuff 77 | 78 | __Pipe__ 79 | 80 | Starts a thread that will copy input data into an output stream. 81 | 82 | 83 | ``` 84 | Pipe pipe = new Pipe("collect stdout", inputStream, outputStream); 85 | pipe.waitFor(); // if you need, you can wait for the pipe to finish 86 | pipe.close(); // close the pipe (does not close the stream!) 87 | ``` 88 | 89 | __MultiplexOutputStream__ 90 | 91 | Create an `OutputStream` which will write into several output streams sequentially. 92 | 93 | ``` 94 | MultiplexOutputStream plex = new MultiplexOutputStream() 95 | .addOutputStream(outputStream) 96 | .addOutputStream(outputStream2) 97 | .addOutputStream(System.out); 98 | ``` 99 | 100 | Usage: 101 | 102 | ``` 103 | plex.getOutputStreams(); // lists the streams 104 | plex.isEmpty(); // true if no streams 105 | plex.streamCount(); // number of streams 106 | plex.write(data); // MultiplexOutputStream is an OutputStream 107 | ``` 108 | 109 | === Local EventBus 110 | 111 | Create a local `EventBus` by using a builder: 112 | 113 | ``` 114 | EventBus eventBus = new EventBus.Builder() 115 | .onError(new PrintingErrorListener(System.err)) // OPTIONAL: print listener exceptions 116 | .onError(new RethrowingErrorListener()) // OPTIONAL: rethrow listener exceptions immediately (by default) 117 | .id("bus-id") // OPTIONAL: bus id (otherwise a UUID is generated) 118 | .build(); 119 | ``` 120 | 121 | ``` 122 | eventBus.getId(); // returns the eventbus id 123 | ``` 124 | 125 | Implement `EventListener` interface to listen to events 126 | 127 | ``` 128 | EventListener listener = new EventListener() { 129 | @Override 130 | public void onEvent(Event e) { 131 | } 132 | }; 133 | ``` 134 | 135 | Bind your listener to events 136 | 137 | ``` 138 | eventBus.on(listener); // register a listener for all events 139 | eventBus.on("my.event", listener); // register a listener for a specific event 140 | ``` 141 | 142 | You can dump (debug) what is going on 143 | 144 | ``` 145 | eventBus.on(new EventListenerSniffer()); // listener which dumps to the console all events (for debug) 146 | ``` 147 | 148 | Unbind events and listeners 149 | 150 | ``` 151 | eventBus.unbind(listener); // removes a listener from all events 152 | eventBus.unbind("my.event"); // removes all listeners for this event 153 | eventBus.unbind("my.event", listener); // removes a specific listener from an event 154 | ``` 155 | 156 | And, of course, trigger events! 157 | 158 | ``` 159 | eventBus.trigger("my.event"); // can trigger an event 160 | eventBus.trigger("my.event", "some data"); // event with data (must be serializable) 161 | ``` 162 | 163 | Here is what you can do with the `Event` object received by `EventListener` implementations: 164 | 165 | ``` 166 | event.getData(); // the data 167 | event.getData(String.class); // can cast the data in the wanted type 168 | event.getName(); // the name of the event triggered 169 | event.getSource(); // the ID of the eventbus 170 | event.getTimestamp(); // time in ms of the event 171 | event.isUserEvent(); // check if it is a user event. You might listen to system events such as eventbus.server.close, eventbus.client.connect, eventbus.client.disconnect 172 | ``` 173 | 174 | === Remote EventBus 175 | 176 | `RemoteEventBus` have the same builder options that a local `EventBus` but serves as inter-process communication through a socket. One `EventBus` acts as a server and several clients can connect to it. 177 | 178 | Clients cannot talks to each-other. This is only a client-server communication, so any events triggers from a client will arrive on the server and any events triggered from the server will then be propagated to all clients. 179 | 180 | Server creation: 181 | 182 | ``` 183 | EventBusServer server = new EventBusServer.Builder() 184 | .id("peer1") // OPTIONAL: bus id 185 | .bind("0.0.0.0") // OPTIONAL: bind address 186 | .listen(56789) // OPTIONAL: port to listen to. Default to 56789 187 | .listenRandom() // OPTIONAL: choose a random port for listening 188 | .build(); 189 | ``` 190 | 191 | Client creation 192 | 193 | ``` 194 | EventBusClient client = new EventBusClient.Builder() 195 | .id("peer2") 196 | .connect(56789) // OPTIONAL: port to connect to 197 | .connect("localhost", 56789) // OPTIONAL: port and host to connect to. Default is localhost:56789 198 | .build(); 199 | ``` 200 | 201 | If nothing is given in the builders, `EventBus` will try to use the system property `ipc.bus.host` for the host to connect to and `ipc.bus.port` for the port to connect to (or listen). 202 | 203 | If no system property is found, `localhost` is used for the host and `56789` is used for the port. 204 | 205 | === Process Launching 206 | 207 | Creates a Java process, similar to `ProcessBuilder` but has several improvements to access stdout, stderr and stdin of the process, cache them, forward them, access the process PID, etc. 208 | 209 | ``` 210 | AnyProcess process = AnyProcess.newBuilder() 211 | .command("bash", "-c", "sleep 3; echo $VAR") 212 | .recordStdout() // OPTIONAL: save stdout from process for getStdout() (disabled by default). Disables getInputStream(). 213 | .recordStderr() // OPTIONAL: save stderr from process for getStderr() (disabled by default). Disabled getErrorStream(). 214 | .env("key", "value") // OPTIONAL: add a env. variable 215 | .env(new HashMap()) // OPTIONAL: se ta new env 216 | .pipeStderr() // OPTIONAL: send stderr to the console 217 | .pipeStderr(outputStream) // OPTIONAL: send stderr to a stream. You can both collect and pipe. 218 | .pipeStdout() // OPTIONAL: send stdout to the console 219 | .pipeStdout(outputStream) // OPTIONAL: send stdout to a stream. You can both collect and pipe. 220 | .pipeStdin() // OPTIONAL: will bind process stdin to this process stding 221 | .pipeStdin(inputStream) // OPTIONAL: will bind process stdin to this input stream 222 | .redirectStderr() // OPTIONAL: treat stderr like stdout (both merged into stdout) 223 | .workingDir(new File(".")) // OPTIONAL: change the working directly. Same as current process by default 224 | .build(); 225 | ``` 226 | 227 | Accessible methods: 228 | 229 | ``` 230 | process.destroy(); // destroy (kill with SIGTERM) the process 231 | process.exitValue(); // the process exit value, when available 232 | process.getCommand(); // the process command 233 | process.getErrorStream(); 234 | process.getInputStream(); 235 | process.getOutputStream(); 236 | process.getPid(); // get the process PID 237 | process.getStderr(); // if collected, get the stderr of the process 238 | process.getStderrText(); // if collected, get the stderr of the process as a String 239 | process.getStdout(); // if collected, get the stdout of the process 240 | process.getStdoutText(); // if collected, get the stdout of the process as a String 241 | process.getWorkingDirectory(); 242 | process.isDestroyed(); // check if process is destroyed 243 | process.isRunning(); // check if process is still running 244 | process.waitFor(); // wait and block while process finished 245 | process.waitForTime(1, TimeUnit.MINUTES); // wait for the process to finish or timeout 246 | ``` 247 | 248 | You can also use a Java `Future`: 249 | 250 | ``` 251 | Future future = process.getFuture(); // get a future representing the process execution. You can cancel (=destroy) the process or wait for its completion 252 | ``` 253 | 254 | === Java Process Launching 255 | 256 | Another builder allows you to quickly start a Java main class with specific env and system properties. You can access the same builder methods as above. 257 | 258 | ``` 259 | JavaProcess javaProcess = JavaProcess.newBuilder() 260 | .mainClass("my.corp.Echo") 261 | .addClasspath(Echo.class) // add a classpath entry from a class (find the enclosing jar or folder) 262 | .arguments("one", "two") // add some program arguments 263 | .env("VAR", "Hello") // add some env. variable 264 | .addJvmProp("my.prop", "world") // add some jvm props 265 | .addJvmArg("-Xmx512m") // add some jvm flags 266 | .pipeStdout() // you can access all process builder methods seen above 267 | .pipeStderr() 268 | .recordStdout() 269 | .recordStderr() 270 | .build(); 271 | ``` 272 | 273 | Java home and Java executable can be automatically discovered, but you can override them in the builder. 274 | 275 | ``` 276 | javaProcess.getJavaExecutable(); // automatically resolved from java home, but you can override it in the builder 277 | javaProcess.getJavaHome(); // automatically resolved from java home, but you can override it in the builder 278 | ``` 279 | 280 | === Inter Java Process Communication 281 | 282 | This builder allows you to start any main class linked to a remote `EventBus` to be able to communicate with some other processes. 283 | 284 | __Special events__ 285 | 286 | Each child process will listen to the event `process.exit` so that you can force a child process to exit like this: 287 | 288 | ``` 289 | myProcess.trigger("process.exit"); 290 | ``` 291 | 292 | ``` 293 | // equivalent to 294 | myProcess.trigger("process.exit", 0); 295 | ``` 296 | 297 | ``` 298 | // or with a code: 299 | myProcess.trigger("process.exit", 1); 300 | ``` 301 | 302 | The event `process.exiting` is fired by the child process when exiting. 303 | 304 | When the process has fully exited, an event `process.exited` is fired. 305 | 306 | But if the parent process calls `process.destroy()` to kill the child process, then the event `process.destroyed` will be fired after the process is destroyed by the SIGTERM signal. 307 | 308 | 309 | __Full Example__ 310 | 311 | Create your main class. From there, you can access the `EventBus` statically. The event bus is connected to the parent process. So each event you send will be propagated, and you can listen to events sent by the parent process also. 312 | 313 | ``` 314 | public class EchoEvent2 { 315 | public static void main(String[] args) throws Exception { 316 | 317 | Bus.get().on("ping", new EventListener() { 318 | @Override 319 | public void onEvent(Event e) { 320 | Bus.get().trigger("pong", e.getData()); 321 | } 322 | }); 323 | 324 | Thread.sleep(2000); 325 | } 326 | } 327 | ``` 328 | 329 | Then, just launch this main class by using the `EventJavaProcess` builder. It extends all the `JavaProcess` and `AnyProcess` classes so you may want to also configure additional things. 330 | 331 | ``` 332 | EventJavaProcess process = EventJavaProcess.newBuilder() 333 | .mainClass(EchoEvent2.class) // set main class to start and add it to classpath 334 | .pipeStdout() // echo stdout 335 | .pipeStderr() // echo stderr 336 | .debug() // activate debug mode for ipc eventbus 337 | .build(); 338 | 339 | assertTrue(process.isEventBusConnected()); 340 | ``` 341 | 342 | And communicate with the child process like this: 343 | 344 | ``` 345 | process.on("process.exiting", new EventListener() { 346 | @Override 347 | public void onEvent(Event e) throws Throwable { 348 | System.out.println("Exiting..."); 349 | } 350 | }); 351 | 352 | process.on("process.exited", new EventListener() { 353 | @Override 354 | public void onEvent(Event e) throws Throwable { 355 | System.out.println("Exited."); 356 | } 357 | }); 358 | 359 | process.on("pong", new EventListener() { 360 | @Override 361 | public void onEvent(Event e) throws Throwable { 362 | System.out.println(e.getData()); 363 | } 364 | }); 365 | 366 | process.trigger("ping", "hello"); 367 | process.trigger("process.exit"); 368 | 369 | process.waitFor(); 370 | 371 | ``` 372 | 373 | You should see some output like this: 374 | 375 | ``` 376 | 1440379569484 [11842] [main] ping@11842 at 1440379569484 - hello 377 | 1440379569485 [11844] [client-acceptor] eventbus.client.connect@11844 at 1440379569484 - localhost:62978 378 | EchoEvent: Event{name='eventbus.client.connect', source=11844, data=localhost:62978} 379 | 1440379569489 [11842] [main] exit@11842 at 1440379569489 380 | 1440379569496 [11844] [reader@localhost:62978] pong@11844 at 1440379569496 - hello 381 | EchoEvent: Event{name='pong', source=11844, data=hello} 382 | 1440379569499 [11844] [reader@localhost:62978] ping@11842 at 1440379569484 - hello 383 | 1440379569500 [11842] [reader@11842] pong@11844 at 1440379569496 - hello 384 | EchoEvent: Event{name='ping', source=11842, data=hello} 385 | 1440379569842 [11842] [reader@11842] eventbus.client.disconnect@11842 at 1440379569842 386 | ``` 387 | --------------------------------------------------------------------------------