001package jmri.server.web.app;
002
003import com.fasterxml.jackson.databind.JsonNode;
004import com.fasterxml.jackson.databind.ObjectMapper;
005import java.io.File;
006import java.io.IOException;
007import java.net.URI;
008import java.net.URL;
009import java.util.ArrayList;
010import java.util.HashSet;
011import java.util.List;
012import java.util.Locale;
013import java.util.Set;
014import jmri.server.web.spi.AngularRoute;
015import jmri.server.web.spi.WebManifest;
016import jmri.server.web.spi.WebMenuItem;
017import jmri.util.FileUtil;
018import org.openide.util.lookup.ServiceProvider;
019import org.slf4j.Logger;
020import org.slf4j.LoggerFactory;
021
022/**
023 * Web Manifest built from manifest.json files.
024 *
025 * @author Randall Wood (C) 2016
026 */
027@ServiceProvider(service = WebManifest.class)
028public class JsonManifest implements WebManifest {
029
030    private boolean initialized = false;
031    private final Set<WebMenuItem> menuItems = new HashSet<>();
032    private final List<String> scripts = new ArrayList<>();
033    private final List<String> styles = new ArrayList<>();
034    private final List<String> dependencies = new ArrayList<>();
035    private final Set<AngularRoute> routes = new HashSet<>();
036    private final List<URL> sources = new ArrayList<>();
037    private final Set<String> translations = new HashSet<>();
038    private final static Logger log = LoggerFactory.getLogger(JsonManifest.class);
039
040    @Override
041    public Set<WebMenuItem> getNavigationMenuItems() {
042        this.initialize();
043        return this.menuItems;
044    }
045
046    @Override
047    public List<String> getScripts() {
048        this.initialize();
049        return this.scripts;
050    }
051
052    @Override
053    public List<String> getStyles() {
054        this.initialize();
055        return this.styles;
056    }
057
058    @Override
059    public List<String> getAngularDependencies() {
060        this.initialize();
061        return this.dependencies;
062    }
063
064    @Override
065    public Set<AngularRoute> getAngularRoutes() {
066        this.initialize();
067        return this.routes;
068    }
069
070    @Override
071    public List<URL> getAngularSources() {
072        this.initialize();
073        return this.sources;
074    }
075
076    @Override
077    public Set<URI> getPreloadedTranslations(Locale locale) {
078        this.initialize();
079        Set<URI> found = new HashSet<>();
080        this.translations.forEach((translation) -> {
081            URI url = FileUtil.findURI(translation.replaceFirst("\\*", locale.toString()));
082            if (url == null) {
083                url = FileUtil.findURI(translation.replaceFirst("\\*", locale.getLanguage()));
084            }
085            if (url == null) {
086                url = FileUtil.findURI(translation.replaceFirst("\\*", "en"));
087            }
088            if (url != null) {
089                found.add(url);
090            } else {
091                log.error("Unable to find localization file {} for any language", translation);
092            }
093        });
094        return found;
095    }
096
097    synchronized private void initialize() {
098        if (!this.initialized) {
099            Set<File> manifests = FileUtil.findFiles("manifest.json", "web"); // NOI18N
100            ObjectMapper mapper = new ObjectMapper();
101            manifests.forEach((manifest) -> {
102                try {
103                    JsonNode root = mapper.readTree(manifest);
104                    root.path("scripts").forEach((script) -> {
105                        if (!this.scripts.contains(script.asText())) {
106                            this.scripts.add(script.asText());
107                        }
108                    });
109                    root.path("styles").forEach((style) -> {
110                        if (!this.styles.contains(style.asText())) {
111                            this.styles.add(style.asText());
112                        }
113                    });
114                    root.path("dependencies").forEach((dependency) -> {
115                        if (!this.dependencies.contains(dependency.asText())) {
116                            this.dependencies.add(dependency.asText());
117                        }
118                    });
119                    root.path("navigation").forEach((navigation) -> {
120                        JsonMenuItem menuItem = new JsonMenuItem(navigation);
121                        if (!this.menuItems.contains(menuItem)) {
122                            this.menuItems.add(menuItem);
123                        }
124                    });
125                    root.path("routes").forEach((route) -> {
126                        String when = route.path("when").asText(); // NOI18N
127                        String template = route.path("template").asText(); // NOI18N
128                        String controller = route.path("controller").asText(); // NOI18N
129                        String redirection = route.path("redirection").asText(); // NOI18N
130                        if (template.isEmpty() || controller.isEmpty()) {
131                            template = null;
132                            controller = null;
133                        }
134                        if (redirection.isEmpty()) {
135                            redirection = null;
136                        }
137                        if (when != null && !when.isEmpty()) {
138                            try {
139                                this.routes.add(new AngularRoute(when, template, controller, redirection));
140                            } catch (NullPointerException | IllegalArgumentException ex) {
141                                log.error("Unable to add route for {}", when);
142                            }
143                        }
144                    });
145                    root.path("sources").forEach((source) -> {
146                        URL url = FileUtil.findURL(source.asText());
147                        if (url != null) {
148                            this.sources.add(url);
149                        }
150                    });
151                    root.path("translations").forEach((translation) -> {
152                        String url = translation.asText();
153                        if (url != null) {
154                            this.translations.add(url);
155                        }
156                    });
157                } catch (IOException ex) {
158                    log.error("Unable to read {}", manifest, ex);
159                }
160            });
161            this.initialized = true;
162        }
163    }
164
165}