001package org.nasdanika.html.model.app.gen.maven;
002
003import java.io.File;
004import java.io.IOException;
005import java.io.UnsupportedEncodingException;
006import java.net.URLEncoder;
007import java.nio.charset.StandardCharsets;
008import java.util.Collection;
009import java.util.HashMap;
010import java.util.LinkedHashMap;
011import java.util.List;
012import java.util.Map;
013import java.util.Map.Entry;
014
015import org.apache.maven.plugin.MojoExecutionException;
016import org.apache.maven.plugins.annotations.LifecyclePhase;
017import org.apache.maven.plugins.annotations.Mojo;
018import org.apache.maven.plugins.annotations.Parameter;
019import org.eclipse.emf.common.util.DiagnosticException;
020import org.eclipse.emf.common.util.URI;
021import org.eclipse.emf.ecore.EObject;
022import org.eclipse.emf.ecore.resource.Resource;
023import org.eclipse.emf.ecore.resource.ResourceSet;
024import org.json.JSONObject;
025import org.nasdanika.common.BiSupplier;
026import org.nasdanika.common.Context;
027import org.nasdanika.common.DefaultConverter;
028import org.nasdanika.common.DiagramGenerator;
029import org.nasdanika.common.DiagramGeneratorImpl;
030import org.nasdanika.common.NasdanikaException;
031import org.nasdanika.common.ProgressMonitor;
032import org.nasdanika.common.Util;
033import org.nasdanika.html.model.app.Action;
034import org.nasdanika.html.model.app.Label;
035import org.nasdanika.html.model.app.Link;
036import org.nasdanika.html.model.app.gen.ActionSiteGenerator;
037import org.nasdanika.html.model.app.gen.SemanticMapResourceFactory;
038import org.nasdanika.maven.AbstractCommandMojo;
039import org.nasdanika.ncore.ModelElement;
040
041/**
042 * Generates action site.
043 */
044@Mojo(name = "generate-action-site", defaultPhase = LifecyclePhase.SITE)
045public class ActionSiteGeneratorMojo extends AbstractCommandMojo {
046
047        /**
048         * Directory to output generated site
049         */
050        @Parameter(defaultValue = "target/action-site") 
051        private File outputDirectory;
052
053        /**
054         * Working directory for storing intermediate files
055         */
056        @Parameter(defaultValue = "target/action-site-work-dir")        
057        private File workDirectory;
058        
059        /**
060         * If true the working directory is cleaned before generation
061         */
062        @Parameter      
063        private boolean cleanWorkDir;   
064        
065        /**
066         * URI of the action from which to generate a site. Resolved relative to the project base directory
067         */
068        @Parameter(required = true)     
069        private String action;
070                
071        /**
072         * URI of the page template. Resolved relative to the project base directory
073         */
074        @Parameter(required = true)     
075        private String pageTemplate;
076        
077        /**
078         * Site map domain/URL to output to sitemap.xml. 
079         * Site map is not generated if this parameter is not set.
080         */
081        @Parameter      
082        private String siteMapDomain;
083        
084        /**
085         * URL of the Drawio viewer script for rendering Drawio diagrams.
086         * Set if you are hosting your own Drawio site.
087         */
088        @Parameter      
089        private String drawioViewer;    
090        
091        /**
092         * Number of known/expected errors. E.g. some blank pages or broken links. Build fails if the number of actual errors reported is different from this parameter. 
093         */
094        @Parameter(required = false)    
095        private int errors;
096        
097        /**
098         * Pluggable diagram generators.
099         */
100    @Parameter
101    private List<DiagramGenerator> diagramGenerators;
102    
103    /**
104     * URL's of YAML or JSON resources which are loaded as semantic maps of URI's to a map containing 'text', 'icon', 'tooltip', 'location' keys.
105     * Semantic maps are used to link model elements to externally defined element by URI. It is similar to how Java modules require other modules and then 
106     * classes in that module may reference elements from the required modules by their fully qualified names.
107     * if the URL ends with .yaml or .yml then it is treated as YAML, as a JSON object otherwise. 
108     */
109    @Parameter
110    private List<String> semanticMaps;
111        
112        @Override
113        protected void execute(Context context, ProgressMonitor progressMonitor) {
114                
115                ActionSiteGenerator actionSiteGenerator = new ActionSiteGenerator() {
116                        
117                        Map<ModelElement, Label> semanticMap = new LinkedHashMap<>();                   
118                        
119                        @Override
120                        protected ResourceSet createResourceSet(Context context, ProgressMonitor progressMonitor) {
121                                ResourceSet resourceSet = super.createResourceSet(context, progressMonitor);
122                                if (semanticMaps != null && !semanticMaps.isEmpty()) {
123                                        SemanticMapResourceFactory smrf = new SemanticMapResourceFactory() {
124                                                @Override
125                                                protected void onLoad(Map<ModelElement, Label> resourceSemanticMap, Resource resource) {
126                                                        super.onLoad(resourceSemanticMap, resource);
127                                                        semanticMap.putAll(resourceSemanticMap);
128                                                }
129                                        };
130                                        resourceSet.getResourceFactoryRegistry().getProtocolToFactoryMap().put("semantic-map", smrf);                           
131                                        
132                                        File baseDir = project.getBasedir();
133                                        URI baseDirURI = URI.createFileURI(baseDir.getAbsolutePath()).appendSegment("");
134                                        
135                                        for (String smLocation: semanticMaps) {
136                                                URI semanticMapURI = URI.createURI(smLocation).resolve(baseDirURI);
137                                                try {
138                                                        URI sMapURI = URI.createURI("semantic-map:" + URLEncoder.encode(semanticMapURI.toString(), StandardCharsets.UTF_8.name()));
139                                                        resourceSet.getResource(sMapURI, true);
140                                                } catch (UnsupportedEncodingException e) {
141                                                        getLog().error("Error loading semantic map " + smLocation, e);
142                                                        throw new NasdanikaException(e);
143                                                }
144                                        }
145                                }
146                                
147                                return resourceSet;
148                        }                       
149                        
150                        @Override
151                        protected void buildRegistry(Action action, Map<EObject, Label> registry) {
152                                registry.putAll(semanticMap);
153                                super.buildRegistry(action, registry);
154                        }
155                        
156                        @Override
157                        protected boolean isSemanticMapLink(Link link) {
158                                return semanticMap.values().contains(link);
159                        }                       
160                        
161                        @Override
162                        protected Context createContext(ProgressMonitor progressMonitor) {
163                                DiagramGenerator diagramGenerator;
164                                if (Util.isBlank(drawioViewer)) {
165                                        diagramGenerator = DiagramGenerator.INSTANCE;
166                                } else {
167                                        diagramGenerator = new DiagramGeneratorImpl() {
168                                                
169                                                protected String getDrawioViewer() {
170                                                        return drawioViewer;
171                                                };
172                                                
173                                        };
174                                }
175                                if (diagramGenerators != null) {
176                                        for (DiagramGenerator dg: diagramGenerators) {
177                                                diagramGenerator = dg.compose(diagramGenerator);
178                                        }
179                                }
180                                return context.compose(Context.singleton(DiagramGenerator.class, diagramGenerator)).compose(super.createContext(progressMonitor));
181                        }
182                        
183                        @Override
184                        protected ProgressMonitor createProgressMonitor() {
185                                return progressMonitor;
186                        }
187                                                
188                };
189                
190                File baseDir = project.getBasedir();
191                URI baseDirURI = URI.createFileURI(baseDir.getAbsolutePath()).appendSegment("");
192                
193                URI actionURI = URI.createURI(action).resolve(baseDirURI);
194                URI pageTemplateURI = URI.createURI(pageTemplate).resolve(baseDirURI);
195
196                try {
197                        Map<String, Collection<String>> errors = actionSiteGenerator.generate(
198                                        actionURI, 
199                                        pageTemplateURI, 
200                                        siteMapDomain, 
201                                        outputDirectory, 
202                                        workDirectory, 
203                                        cleanWorkDir);
204                        
205                        int errorCount = 0;                     
206                        for (Entry<String, Collection<String>> ee: errors.entrySet()) {
207                                getLog().error(ee.getKey());
208                                for (String error: ee.getValue()) {
209                                        ++errorCount;
210                                        getLog().error("\t" + error);
211                                }
212                        }
213                        if (errorCount != this.errors) {
214                                String message = "There are " + errorCount + " site errors";
215                                getLog().error(message);
216                                throw new NasdanikaException(message);
217                        }
218                } catch (IOException | DiagnosticException ex) {
219                        throw new NasdanikaException(ex);
220                }
221        }
222
223}