001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028package edu.wisc.ssec.mcidasv.servermanager;
029
030import java.io.InputStream;
031import java.io.InputStreamReader;
032
033import org.bushe.swing.event.EventBus;
034
035import edu.wisc.ssec.mcidasv.Constants;
036import edu.wisc.ssec.mcidasv.McIDASV;
037
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041/**
042 * Thread that actually execs the {@literal "mcservl"} process.
043 */
044public class AddeThread extends Thread {
045
046    private static final Logger logger = LoggerFactory.getLogger(AddeThread.class);
047
048    /** Mcserv events. */
049    public enum McservEvent { 
050        /** Mcservl is actively listening. */
051        ACTIVE("Local servers are running."),
052        /** Mcservl has died unexpectedly. */
053        DIED("Local servers quit unexpectedly."),
054        /** Mcservl started listening. */
055        STARTED("Local servers started listening on port %s."),
056        /** Mcservl has stopped listening. */
057        STOPPED("Local servers have been stopped.");
058
059        /** */
060        private final String message;
061
062        /**
063         * Creates an object that represents the status of a mcservl process.
064         * 
065         * @param message Should not be {@code null}.
066         */
067        McservEvent(final String message) {
068            this.message = message;
069        }
070
071        /**
072         * 
073         * 
074         * @return Format string associated with an event.
075         */
076        public String getMessage() {
077            return message;
078        }
079    }
080
081    /** */
082    Process proc;
083
084    /** Server manager. */
085    private final EntryStore entryStore;
086
087    /**
088     * Creates a thread that controls a mcservl process.
089     * 
090     * @param entryStore Server manager.
091     */
092    public AddeThread(final EntryStore entryStore) {
093        this.entryStore = entryStore;
094    }
095
096    public void run() {
097        StringBuilder err = new StringBuilder();
098        String[] cmds = entryStore.getAddeCommands();
099        String[] env = (McIDASV.isWindows()) ? entryStore.getWindowsAddeEnv() : entryStore.getUnixAddeEnv();
100
101        StringBuilder temp = new StringBuilder(512);
102        for (String cmd : cmds) {
103            temp.append(cmd).append(' ');
104        }
105        logger.info("Starting mcservl: {}", temp.toString());
106        temp = new StringBuilder(1024).append("{ ");
107        for (String e : env) {
108            temp.append(e).append(", ");
109        }
110        temp.append('}');
111        logger.debug("env={}", temp.toString());
112//        temp = null;
113
114        try {
115            //start ADDE binary with "-p PORT" and set environment appropriately
116            proc = Runtime.getRuntime().exec(cmds, env);
117
118            //create thread for reading inputStream (process' stdout)
119            StreamReaderThread outThread = new StreamReaderThread(proc.getInputStream(), new StringBuilder());
120
121            //create thread for reading errorStream (process' stderr)
122            StreamReaderThread errThread = new StreamReaderThread(proc.getErrorStream(), err);
123
124            //start both threads
125            outThread.start();
126            errThread.start();
127
128            //wait for process to end
129            int result = proc.waitFor();
130
131            //finish reading whatever's left in the buffers
132            outThread.join();
133            errThread.join();
134
135            if (result != 0) {
136//                entryStore.stopLocalServer(entryStore.getRestarting());
137                entryStore.stopLocalServer();
138                String errString = err.toString();
139
140                // If the server couldn't start for a known reason, try again on another port
141                //  Retry up to 10 times
142                if (((result == 35584) || (errString.contains("Error binding to port"))) &&
143                    (Integer.parseInt(EntryStore.getLocalPort()) < (Integer.parseInt(Constants.LOCAL_ADDE_PORT) + 30)))
144                {
145                    EntryStore.setLocalPort(EntryStore.nextLocalPort());
146//                    entryStore.startLocalServer(entryStore.getRestarting());
147                    entryStore.startLocalServer();
148                }
149            }
150        } catch (InterruptedException e) {
151//            McservEvent type = McservEvent.DIED;
152////            if (entryStore.getRestarting()) {
153//                type = McservEvent.STARTED;
154////            }
155//            EventBus.publish(type);
156            EventBus.publish(McservEvent.STARTED);
157        } catch (Exception e) {
158            EventBus.publish(McservEvent.DIED);
159        }
160    }
161
162    /**
163     * 
164     */
165    public void stopProcess() {
166        proc.destroy();
167    }
168
169//    /**
170//     * 
171//     */
172//    public String toString() {
173//        return String.format("[AddeThread@%x: ADDE_ENV=%s, ADDE_COMMANDS=%s]", hashCode(), ADDE_ENV, ADDE_COMMANDS);
174//    }
175
176    /**
177     * Thread to read the stderr and stdout of mcservl.
178     */
179    private static class StreamReaderThread extends Thread {
180
181        /** stderr */
182        private final StringBuilder mOut;
183
184        /** stdin */
185        private final InputStreamReader mIn;
186
187        /**
188         * Create a thread for reading mcservl output.
189         *
190         * @param in Input {@literal "stream"}.
191         * @param out Output {@literal "stream"}.
192         */
193        public StreamReaderThread(final InputStream in, final StringBuilder out) {
194            mOut = out;
195            mIn = new InputStreamReader(in);
196        }
197
198        /**
199         * Reads {@link #mIn} and appends to {@link #mOut}.
200         */
201        public void run() {
202            int ch;
203            try {
204                while (-1 != (ch = mIn.read())) {
205                    mOut.append((char)ch);
206                }
207            } catch (Exception e) {
208                mOut.append("\nRead error: ").append(e.getMessage());
209            }
210        }
211    }
212}