]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.maps.server/src/org/simantics/district/maps/server/TileserverMapnik.java
Elimination of compiler warnings.
[simantics/district.git] / org.simantics.maps.server / src / org / simantics / district / maps / server / TileserverMapnik.java
1 package org.simantics.district.maps.server;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.nio.charset.StandardCharsets;
6 import java.nio.file.DirectoryStream;
7 import java.nio.file.Files;
8 import java.nio.file.Path;
9 import java.nio.file.StandardOpenOption;
10 import java.util.ArrayList;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Optional;
15 import java.util.concurrent.TimeUnit;
16 import java.util.concurrent.atomic.AtomicBoolean;
17 import java.util.stream.Stream;
18
19 import org.simantics.district.maps.server.prefs.MapsServerPreferences;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22 import org.yaml.snakeyaml.Yaml;
23 import org.zeroturnaround.exec.ProcessExecutor;
24 import org.zeroturnaround.exec.StartedProcess;
25 import org.zeroturnaround.exec.stream.slf4j.Slf4jDebugOutputStream;
26 import org.zeroturnaround.process.PidProcess;
27 import org.zeroturnaround.process.PidUtil;
28 import org.zeroturnaround.process.Processes;
29 import org.zeroturnaround.process.SystemProcess;
30
31 import com.fasterxml.jackson.core.JsonParseException;
32 import com.fasterxml.jackson.databind.JsonMappingException;
33 import com.fasterxml.jackson.databind.ObjectMapper;
34
35 public class TileserverMapnik {
36
37     private static final Logger LOGGER = LoggerFactory.getLogger(TileserverMapnik.class);
38     
39     private SystemProcess process;
40     private Path serverRoot;
41     
42     private AtomicBoolean running = new AtomicBoolean(false);
43     
44     TileserverMapnik(Path serverRoot) {
45         this.serverRoot = serverRoot.normalize();
46     }
47
48     public boolean isRunning() throws IOException, InterruptedException {
49         return process == null ? false : process.isAlive() && isReady();
50     }
51     
52     public boolean isReady() {
53         return running.get();
54     }
55     
56     public void start(Optional<TileserverStartListener> listener) throws Exception {
57         // check if existing server is left hanging
58         if (Files.exists(getPid())) {
59             String pid = new String(Files.readAllBytes(getPid()));
60             PidProcess pr = Processes.newPidProcess(Integer.parseInt(pid));
61             pr.destroyForcefully();
62         }
63         
64         // check that npm dependencies are satisfied
65 //        if (checkAndInstall(null, listener)) {
66 //            checkAndInstall(ADDITIONAL_DEPENDENCIES[0], listener);
67 //            checkAndInstall(ADDITIONAL_DEPENDENCIES[1], listener);
68 //        }
69         
70         checkConfigJson();
71         checkTm2Styles();
72         
73         if (process != null && process.isAlive())
74             return;
75         
76         Path tileliveTesseraWin = serverRoot.resolve("dist").resolve("node.exe").normalize().toAbsolutePath();
77         StartedProcess startedProcess = new ProcessExecutor().directory(serverRoot.resolve("dist").toFile()).destroyOnExit().environment(getEnv())
78                 .command(tileliveTesseraWin.toString(), "-c", getConfigJson().toString(), "-p", Integer.toString(MapsServerPreferences.defaultPort()))
79                 .redirectOutput(new Slf4jDebugOutputStream(LOGGER) {
80                     
81                     @Override
82                     protected void processLine(String line) {
83                         // Convert to UTF-8 string
84                         String utf8Line = new String(line.getBytes(), StandardCharsets.UTF_8);
85                         log.debug(utf8Line);
86                     }
87                 }).start();
88         
89         Process nativeProcess = startedProcess.getProcess();
90         process = Processes.newStandardProcess(nativeProcess);
91         int pid = PidUtil.getPid(nativeProcess);
92         LOGGER.info("Writing pid-file to {} with pid={}", getPid(), pid);
93         Files.write(getPid(), (pid + "").getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
94         running.set(true);
95         listener.ifPresent(TileserverStartListener::started);
96     }
97
98     private Map<String, String> getEnv() {
99         Map<String, String> env = new HashMap<>();
100         env.put("MAPNIK_FONT_PATH", getFonts().toString());
101         env.put("ICU_DATA", getICU().toString());
102         return env;
103     }
104
105     public void stop() throws IOException, InterruptedException {
106         if (process != null && process.isAlive()) {
107             // gracefully not supported on windows
108             process.destroyForcefully();
109             if (!process.waitFor(2000, TimeUnit.MILLISECONDS)) {
110                 process.destroyForcefully();
111                 if (process.isAlive())
112                     LOGGER.error("Could not shutdown TileserverMapnik!");
113             }
114             process = null;
115             Files.delete(getPid());
116             running.set(false);
117         }
118     }
119
120     private Path getPid() {
121         return serverRoot.resolve("pid");
122     }
123
124     public void restart(Optional<TileserverStartListener> listener) throws Exception {
125         stop();
126         start(listener);
127     }
128     
129 //    private boolean checkIfInstalled(String module) throws Exception {
130 //        String tileserverMapnik = tileserverMapnikRoot().toString();
131 //        int retVal;
132 //        try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
133 //            if (module == null) {
134 //                retVal = NodeJS.npm(output, "--prefix", tileserverMapnik, "list");
135 //            } else {
136 //                retVal = NodeJS.npm(output, "--prefix", tileserverMapnik, "list", module);
137 //            }
138 //            String outputString = new String(output.toByteArray(), StandardCharsets.UTF_8);
139 //        }
140 //        return retVal == 0;
141 //    }
142 //    
143 //    private int install(String module, Optional<TileserverStartListener> listener) throws Exception {
144 //        String tileserverMapnik = tileserverMapnikRoot().toString();
145 //        int retVal;
146 //        if (module == null) {
147 //            listener.ifPresent(l -> l.installing("Installing tileserver-mapnik"));
148 //            retVal = NodeJS.npm(null, "--prefix", tileserverMapnik, "install", "--save");
149 //        } else { 
150 //            retVal = NodeJS.npm(null, "--prefix", tileserverMapnik, "install", module, "--save");
151 //        }
152 //        if (retVal != 0)
153 //            LOGGER.warn("Could not install module " + module == null ? "package.json" : module + "! " + retVal);
154 //        return retVal;
155 //    }
156 //    
157 //    private boolean checkAndInstall(String module, Optional<TileserverStartListener> listener) throws Exception {
158 //        LOGGER.info("Installing module {}", String.valueOf(module));
159 //        boolean installed = checkIfInstalled(module); 
160 //        if (!installed) {
161 //            int installSuccessfull = install(module, listener);
162 //            if (installSuccessfull != 0) {
163 //                LOGGER.warn("Installation of module {} failed!", String.valueOf(module));
164 //                listener.ifPresent(l -> l.installationFailed(module, installSuccessfull));
165 //            }
166 //        } else {
167 //            LOGGER.info("Module {} was already installed", String.valueOf(module));
168 //        }
169 //        return !installed;
170 //    }
171
172     
173     @SuppressWarnings("unused")
174         private Path tileserverMapnikRoot() {
175         return serverRoot.resolve("tileserver-mapnik").toAbsolutePath();
176     }
177     
178     private Path getFonts() {
179         return serverRoot.resolve("fonts").toAbsolutePath();
180     }
181     
182     private Path getICU() {
183         return serverRoot.resolve("dist/share/icu").toAbsolutePath();
184     }
185     
186     @SuppressWarnings("unused")
187         private Path getTessera() {
188         return serverRoot.resolve("tileserver-mapnik/bin/tessera.js").toAbsolutePath();
189     }
190     
191     private Path getConfigJson() {
192         return serverRoot.resolve("config.json").toAbsolutePath();
193     }
194     
195     public List<String> availableMBTiles() throws IOException {
196         Path data = getDataDirectory();
197         List<String> result = new ArrayList<>();
198         try (Stream<Path> paths = Files.walk(data)) {
199             paths.forEach(p -> {
200                 if (!p.equals(data)) {
201                     String tiles = p.getFileName().toString();
202                     result.add(tiles);
203                 }
204             });
205         }
206         return result;
207     }
208     
209     private void checkConfigJson() throws JsonParseException, JsonMappingException, IOException {
210         Path configJson = getConfigJson();
211         Map<String, Object> config = new HashMap<>();
212         Path tm2 = getStyleDirectory();
213         try (DirectoryStream<Path> stream = Files.newDirectoryStream(tm2)) {
214             stream.forEach(p -> {
215                 Path projectYaml = p.resolve("project.yml");
216                 if (Files.exists(projectYaml)) {
217                     // Good
218                     
219                     Map<String, String> source = new HashMap<>();
220                     
221                     Path tm2style = serverRoot.relativize(projectYaml.getParent());
222                     
223                     String prefix = "tmstyle://../";
224                     String tmStyle = prefix + tm2style.toString(); 
225                     source.put("source", tmStyle);
226                     config.put("/" + projectYaml.getParent().getFileName().toString(), source);
227                 }
228             });
229         }
230         ObjectMapper mapper = new ObjectMapper();
231         mapper.writerWithDefaultPrettyPrinter().writeValue(Files.newOutputStream(configJson, StandardOpenOption.TRUNCATE_EXISTING), config);
232     }
233     
234     public Path getStyleDirectory() {
235         return serverRoot.resolve("tm2");
236     }
237     
238     public Path getDataDirectory() {
239         return serverRoot.resolve("data");
240     }
241     
242     public Path getCurrentTiles() {
243         return getDataDirectory().resolve(MapsServerPreferences.currentMBTiles());
244     }
245     
246     @SuppressWarnings("unchecked")
247     public void checkTm2Styles() {
248         Path tm2 = getStyleDirectory();
249         try (DirectoryStream<Path> stream = Files.newDirectoryStream(tm2)) {
250             stream.forEach(p -> {
251                 Path projectYaml = p.resolve("project.yml");
252                 Yaml yaml = new Yaml();
253                 Map<String, String> data = null;
254                 try (InputStream input = Files.newInputStream(projectYaml, StandardOpenOption.READ)) {
255                     data = yaml.loadAs(input, Map.class);
256                     
257                     Path tiles = serverRoot.relativize(getCurrentTiles());
258                     
259                     String tmStyle = "mbtiles://../" + tiles.toString();
260                     data.put("source", tmStyle);
261                     
262                 } catch (IOException e) {
263                     LOGGER.error("Could not read yaml", e);
264                 }
265                 try {
266                     Files.write(projectYaml, yaml.dump(data).getBytes(), StandardOpenOption.TRUNCATE_EXISTING);
267                 } catch (IOException e) {
268                     LOGGER.error("Could not write yaml", e);
269                 }
270             });
271         } catch (IOException e) {
272             LOGGER.error("Could not browse tm2 styles", e);
273         }
274     }
275
276     public List<String> listStyles() throws IOException {
277         List<String> results = new ArrayList<>();
278         Path tm2 = getStyleDirectory();
279         try (DirectoryStream<Path> stream = Files.newDirectoryStream(tm2)) {
280             stream.forEach(p -> {
281                 results.add(p.getFileName().toString());
282             });
283         }
284         return results;
285     }
286
287     public static interface TileserverStartListener {
288         
289         void installing(String module);
290         
291         void installationFailed(String module, int returnValue);
292
293         void started();
294     }
295 }