001/** 002 * Copyright (C) 2006-2024 Talend Inc. - www.talend.com 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.talend.sdk.component.tools; 017 018import static java.util.Optional.ofNullable; 019import static java.util.concurrent.TimeUnit.MINUTES; 020 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Scanner; 024import java.util.concurrent.CountDownLatch; 025import java.util.concurrent.atomic.AtomicReference; 026import java.util.function.Consumer; 027import java.util.stream.Stream; 028 029import org.apache.catalina.core.StandardServer; 030import org.apache.catalina.webresources.StandardRoot; 031import org.apache.meecrowave.Meecrowave; 032import org.apache.meecrowave.runner.Cli; 033 034public class WebServer implements Runnable { 035 036 private final Collection<String> serverArguments; 037 038 private final Integer port; 039 040 private final String componentGav; 041 042 private final Log log; 043 044 private final Collection<Consumer<Meecrowave.Builder>> onOpen = new ArrayList<>(); 045 046 public WebServer(final Collection<String> serverArguments, final Integer port, final Object log, final String gav) { 047 this.serverArguments = serverArguments; 048 this.port = port; 049 try { 050 this.log = Log.class.isInstance(log) ? Log.class.cast(log) : new ReflectiveLog(log); 051 } catch (final NoSuchMethodException e) { 052 throw new IllegalArgumentException(e); 053 } 054 this.componentGav = gav; 055 } 056 057 public WebServer onOpen(final Consumer<Meecrowave.Builder> task) { 058 onOpen.add(task); 059 return this; 060 } 061 062 public WebServer openBrowserWhenReady() { 063 return onOpen(builder -> Browser.open("http://localhost:" + builder.getHttpPort(), log)); 064 } 065 066 @Override 067 public void run() { 068 final String originalCompSystProp = 069 setSystemProperty("talend.component.server.component.coordinates", componentGav); 070 final String skipClasspathSystProp = setSystemProperty("component.manager.classpath.skip", "true"); 071 final String skipCallersSystProp = setSystemProperty("component.manager.callers.skip", "true"); 072 final AtomicReference<Meecrowave> ref = new AtomicReference<>(); 073 try { 074 final CountDownLatch latch = new CountDownLatch(1); 075 new Thread(() -> { 076 try (final Meecrowave meecrowave = new Meecrowave(Cli.create(buildArgs()))) { 077 meecrowave.start().deployClasspath(new Meecrowave.DeploymentMeta("", null, stdCtx -> { 078 stdCtx.setResources(new StandardRoot() { 079 080 @Override 081 protected void registerURLStreamHandlerFactory() { 082 // no-op - gradle supports to reuse the same JVM so it would be broken 083 } 084 }); 085 }, null)); 086 087 ref.set(meecrowave); 088 latch.countDown(); 089 onOpen.forEach(it -> it.accept(meecrowave.getConfiguration())); 090 meecrowave.getTomcat().getServer().await(); 091 } catch (final RuntimeException re) { 092 latch.countDown(); 093 log.error(re.getMessage()); 094 throw re; 095 } 096 }, getClass().getName() + '_' + findPort()).start(); 097 try { 098 latch.await(2, MINUTES); 099 } catch (final InterruptedException e) { 100 Thread.currentThread().interrupt(); 101 return; 102 } 103 final boolean batch = Boolean.parseBoolean(System.getProperty("talend.web.batch", "false")); 104 final int timeout = Integer.parseInt(System.getProperty("talend.web.batch.timeout", "2")); 105 if (!batch) { 106 log.info("\n\n You can now access the UI at http://localhost:" + port + "\n\n"); 107 final Scanner scanner = new Scanner(System.in); 108 do { 109 log.info("Enter 'exit' to quit"); 110 } while (!shouldQuit(scanner.nextLine())); 111 } else { 112 log.info(String.format( 113 "Server running at http://localhost:%d in non-interactive mode. Will shutdown in %d minutes.", 114 port, timeout)); 115 try { 116 Thread.currentThread().sleep(MINUTES.toMillis(timeout)); 117 log.info("Shutting down."); 118 } catch (final InterruptedException e) { 119 Thread.currentThread().interrupt(); 120 return; 121 } 122 } 123 } finally { 124 reset("talend.component.server.component.coordinates", originalCompSystProp); 125 reset("component.manager.classpath.skip", skipClasspathSystProp); 126 reset("component.manager.callers.skip", skipCallersSystProp); 127 ofNullable(ref.get()).ifPresent(mw -> StandardServer.class.cast(mw.getTomcat().getServer()).stopAwait()); 128 } 129 } 130 131 private String setSystemProperty(final String key, final String value) { 132 final String old = System.getProperty(key); 133 System.setProperty(key, value); 134 return old; 135 } 136 137 private void reset(final String key, final String value) { 138 if (value == null) { 139 System.clearProperty(key); 140 } else { 141 System.setProperty(key, value); 142 } 143 } 144 145 private boolean shouldQuit(final String value) { 146 return Stream.of("exit", "quit", "X").anyMatch(v -> v.equalsIgnoreCase(value)); 147 } 148 149 private String[] buildArgs() { 150 final Collection<String> args = new ArrayList<>(); 151 if (serverArguments != null) { 152 args.addAll(serverArguments); 153 } 154 if (serverArguments != null && serverArguments.contains("--http")) { 155 if (port != null) { 156 log.info("port configuration ignored since serverArguments already defines it"); 157 } 158 } else { 159 args.add("--http"); 160 args.add(findPort()); 161 } 162 if (!args.contains("--scanning-exclude")) { // nicer default logging 163 args.add("--scanning-exclude"); 164 args 165 .add("animal-sniffer-annotations,checker-qual,component-form,component-server-model," 166 + "error_prone_annotations,failureaccess,freemarker,j2objc-annotations,jib-core," 167 + "jsr305,listenablefuture,talend-component-maven-plugin," 168 + "avro,beam,paranamer,xz,component-api,component-spi,component-runtime-impl," 169 + "component-runtime-manager,component-runtime-design-extension,container-core," 170 + "component-runtime-beam"); 171 } 172 if (!args.contains("--use-shutdown-hook")) { 173 args.add("--use-shutdown-hook"); 174 args.add("false"); 175 } 176 return args.toArray(new String[0]); 177 } 178 179 private String findPort() { 180 return port == null ? "8080" : Integer.toString(port); 181 } 182}