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}