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