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}