001package org.nasdanika.html.model.app.gen.maven; 002 003import java.io.File; 004import java.io.IOException; 005import java.net.URL; 006import java.util.ArrayList; 007import java.util.Collection; 008import java.util.List; 009import java.util.Map; 010import java.util.Map.Entry; 011import java.util.function.Function; 012import java.util.Objects; 013import java.util.stream.Collectors; 014 015import org.apache.maven.plugins.annotations.LifecyclePhase; 016import org.apache.maven.plugins.annotations.Mojo; 017import org.apache.maven.plugins.annotations.Parameter; 018import org.eclipse.emf.common.util.DiagnosticException; 019import org.eclipse.emf.common.util.URI; 020import org.eclipse.emf.ecore.EObject; 021import org.nasdanika.common.Context; 022import org.nasdanika.common.DiagramGenerator; 023import org.nasdanika.common.DiagramGeneratorImpl; 024import org.nasdanika.common.NasdanikaException; 025import org.nasdanika.common.ProgressMonitor; 026import org.nasdanika.common.Util; 027import org.nasdanika.drawio.Document; 028import org.nasdanika.html.emf.RepresentationProcessor; 029import org.nasdanika.html.model.app.Action; 030import org.nasdanika.html.model.app.Link; 031import org.nasdanika.html.model.app.gen.ActionSiteGenerator; 032import org.nasdanika.html.model.app.gen.SiteGeneratorContributor; 033import org.nasdanika.maven.AbstractCommandMojo; 034import org.nasdanika.ncore.util.SemanticInfo; 035import org.nasdanika.ncore.util.SemanticRegistry; 036 037/** 038 * Generates action site. 039 */ 040@Mojo(name = "generate-action-site", defaultPhase = LifecyclePhase.SITE) 041public class ActionSiteGeneratorMojo extends AbstractCommandMojo { 042 043 /** 044 * Directory to output generated site 045 */ 046 @Parameter(defaultValue = "target/action-site") 047 private File outputDirectory; 048 049 /** 050 * Working directory for storing intermediate files 051 */ 052 @Parameter(defaultValue = "target/action-site-work-dir") 053 private File workDirectory; 054 055 /** 056 * If true the working directory is cleaned before generation 057 */ 058 @Parameter 059 private boolean cleanWorkDir; 060 061 /** 062 * URI of the action from which to generate a site. Resolved relative to the project base directory 063 */ 064 @Parameter(required = true) 065 private String action; 066 067 /** 068 * URI of the page template. Resolved relative to the project base directory 069 */ 070 @Parameter(required = true) 071 private String pageTemplate; 072 073 /** 074 * Site map domain/URL to output to sitemap.xml. 075 * Site map is not generated if this parameter is not set. 076 */ 077 @Parameter 078 private String siteMapDomain; 079 080 /** 081 * URL of the Drawio viewer script for rendering Drawio diagrams. 082 * Set if you are hosting your own Drawio site. 083 */ 084 @Parameter 085 private String drawioViewer; 086 087 /** 088 * 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. 089 */ 090 @Parameter(required = false) 091 private int errors; 092 093 /** 094 * Pluggable diagram generators. 095 */ 096 @Parameter 097 private List<DiagramGenerator> diagramGenerators; 098 099 /** 100 * Contributors to site generation. 101 */ 102 @Parameter 103 private List<SiteGeneratorContributor> contributors; 104 105 /** 106 * URL's of JSON resources with information about external semantic elements. Such JSON resources are created as part of site generation. 107 * They are named semantic-info.json 108 * Semantic infos are used to link model elements to externally defined element by URI. It is similar to how Java modules require other modules and then 109 * classes in that module may reference elements from the required modules by their fully qualified names. 110 * 111 * Semantic info may be created programmatically using {@link SemanticInfo} and {@link SemanticRegistry} classes. 112 */ 113 @Parameter 114 private List<String> semanticInfos; 115 116 /** 117 * If true, generation is performed in parallel threads. 118 */ 119 @Parameter(required = false) 120 protected boolean parallel; 121 122 @Override 123 protected void execute(Context context, ProgressMonitor progressMonitor) { 124 ActionSiteGenerator actionSiteGenerator = createActionSiteGenerator(context, progressMonitor); 125 126 File baseDir = project.getBasedir(); 127 URI baseDirURI = URI.createFileURI(baseDir.getAbsolutePath()).appendSegment(""); 128 129 URI actionURI = URI.createURI(action).resolve(baseDirURI); 130 URI pageTemplateURI = URI.createURI(pageTemplate).resolve(baseDirURI); 131 132 try { 133 Map<String, Collection<String>> errors = actionSiteGenerator.generate( 134 actionURI, 135 pageTemplateURI, 136 siteMapDomain, 137 outputDirectory, 138 workDirectory, 139 cleanWorkDir); 140 141 int errorCount = 0; 142 for (Entry<String, Collection<String>> ee: errors.entrySet()) { 143 getLog().error(ee.getKey()); 144 for (String error: ee.getValue()) { 145 ++errorCount; 146 getLog().error("\t" + error); 147 } 148 } 149 if (errorCount != this.errors) { 150 String message = "There are " + errorCount + " site errors"; 151 getLog().error(message); 152 throw new NasdanikaException(message); 153 } 154 } catch (IOException | DiagnosticException ex) { 155 throw new NasdanikaException(ex); 156 } 157 } 158 159 protected ActionSiteGenerator createActionSiteGenerator(Context context, ProgressMonitor progressMonitor) { 160 File baseDir = project.getBasedir(); 161 URI baseDirURI = URI.createFileURI(baseDir.getAbsolutePath()).appendSegment(""); 162 SemanticRegistry semanticRegistry = new SemanticRegistry(); 163 if (semanticInfos != null) { 164 for (String smLocation: semanticInfos) { 165 URI semanticInfoURI = URI.createURI(smLocation).resolve(baseDirURI); 166 try { 167 semanticRegistry.load(new URL(semanticInfoURI.toString())); 168 } catch (IOException e) { 169 String message = "Could not load semantic info from " + semanticInfoURI; 170 getLog().error(message, e); 171 throw new NasdanikaException(message, e); 172 } 173 } 174 } 175 176 return new ActionSiteGenerator() { 177 178 { 179 this.parallel = ActionSiteGeneratorMojo.this.parallel; 180 } 181 182 @Override 183 protected Iterable<SemanticInfo> getSemanticInfos() { 184 return semanticRegistry 185 .stream() 186 .filter(SemanticInfo.class::isInstance) 187 .map(SemanticInfo.class::cast) 188 .toList(); 189 } 190 191 @Override 192 protected boolean isSemanticInfoLink(Link link) { 193 if (link == null || Util.isBlank(link.getLocation())) { 194 return false; 195 } 196 String linkLocation = link.getLocation(); 197 return semanticRegistry 198 .stream() 199 .filter(SemanticInfo.class::isInstance) 200 .map(SemanticInfo.class::cast) 201 .map(SemanticInfo::getLocation) 202 .filter(Objects::nonNull) 203 .map(Object::toString) 204 .filter(linkLocation::equals) 205 .findFirst() 206 .isPresent(); 207 } 208 209 @Override 210 protected Context createContext(ProgressMonitor progressMonitor) { 211 DiagramGenerator diagramGenerator; 212 if (Util.isBlank(drawioViewer)) { 213 diagramGenerator = DiagramGenerator.INSTANCE; 214 } else { 215 diagramGenerator = new DiagramGeneratorImpl() { 216 217 protected String getDrawioViewer() { 218 return drawioViewer; 219 }; 220 221 }; 222 } 223 if (diagramGenerators != null) { 224 for (DiagramGenerator dg: diagramGenerators) { 225 diagramGenerator = dg.compose(diagramGenerator); 226 } 227 } 228 229 RepresentationProcessor representationProcessor = new RepresentationProcessor() { 230 231 @Override 232 public Document processDrawioRepresentation( 233 Document document, 234 Action action, 235 Function<URI, EObject> semanticLinkResolver, 236 org.nasdanika.html.emf.EObjectActionResolver.Context context, 237 ProgressMonitor progressMonitor) { 238 239 for (SiteGeneratorContributor contributor: getContributors()) { 240 document = contributor.processDrawioRepresentation(document, action, semanticLinkResolver, context, progressMonitor); 241 } 242 243 return document; 244 } 245 246 }; 247 248 return context 249 .compose(Context.singleton(DiagramGenerator.class, diagramGenerator)) 250 .compose(Context.singleton(RepresentationProcessor.class, representationProcessor)) 251 .compose(super.createContext(progressMonitor)); 252 } 253 254 @Override 255 protected ProgressMonitor createProgressMonitor() { 256 return progressMonitor; 257 } 258 259 @Override 260 protected Collection<SiteGeneratorContributor> getContributors() { 261 Collection<SiteGeneratorContributor> allContributors = new ArrayList<>(super.getContributors()); 262 if (ActionSiteGeneratorMojo.this.contributors != null) { 263 allContributors.addAll(ActionSiteGeneratorMojo.this.contributors); 264 } 265 return allContributors; 266 } 267 268 }; 269 } 270 271}