View Javadoc

1   /*
2    * Copyright 2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 1.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl1.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.student.datadictionary.mojo;
17  
18  import java.io.File;
19  import java.io.FileNotFoundException;
20  import java.io.FileOutputStream;
21  import java.io.OutputStream;
22  import java.io.PrintStream;
23  import java.net.MalformedURLException;
24  import java.net.URL;
25  import java.net.URLClassLoader;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.LinkedHashMap;
29  import java.util.LinkedHashSet;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  
34  import org.apache.maven.artifact.DependencyResolutionRequiredException;
35  import org.apache.maven.plugin.AbstractMojo;
36  import org.apache.maven.plugin.MojoExecutionException;
37  import org.apache.maven.project.MavenProject;
38  import org.joda.time.DateTime;
39  import org.kuali.student.contract.model.MessageStructure;
40  import org.kuali.student.contract.model.ServiceContractModel;
41  import org.kuali.student.contract.model.impl.ServiceContractModelCache;
42  import org.kuali.student.contract.model.impl.ServiceContractModelQDoxLoader;
43  import org.kuali.student.contract.model.util.DateUtility;
44  import org.kuali.student.contract.model.util.VersionLinesUtility;
45  import org.kuali.student.contract.model.validation.ServiceContractModelValidator;
46  import org.kuali.student.datadictionary.util.DictionaryFormatter;
47  import org.kuali.student.datadictionary.util.DictionaryTesterHelper;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  /**
52   * Mojo for generating a formatted view of the data dictionary.
53   * 
54   * <pre>
55   * {@code
56   * <plugin>
57   * 		<groupId>org.kuali.maven.plugins</groupId>
58   *      <artifactId>maven-kscontractdoc-plugin</artifactId>
59   *      <execution>
60   *      	<id>generate-dictionary-documentation</id>
61   *          <phase>site</phase>
62   *          <goals>
63   *          	<goal>ksdictionarydoc</goal>                            
64   *          </goals>
65   *          <configuration>
66   *           <supportFiles>
67   *           	<supportFile>commonApplicationContext.xml</supportFile>
68   *           </supportFiles>
69   *          </configuration>
70   *     </execution>
71   * </plugin>
72   *  }
73   * </pre>
74   * 
75   * We use the QDox model to read the class files present and to enumerate the list of Message Structure objects.  Then for each identified type we build an application context that includes the union of that 
76   * file plus any files specified using the <supportFile> configuration parameter.
77   * 
78   *  Errors with an application context are detected and logged but will not break the plugin's ability to generate the other files.
79   * 
80   * @goal ksdictionarydoc
81   * @phase site
82   * @requiresDependencyResolution test
83   */
84  public class KSDictionaryDocMojo extends AbstractMojo {
85  
86  	private static final Logger log = LoggerFactory.getLogger(KSDictionaryDocMojo.class);
87  	
88      /**
89       * @parameter expression="${project}"
90       * @required
91       * @readonly
92       */
93      private MavenProject project;
94      
95      /**
96       * @parameter
97       **/
98      private List<String> sourceDirs;
99      
100     /**
101      * The base applicationContext files.  
102      * @parameter
103      **/
104     private List<String> supportFiles = new ArrayList<String>();
105     /**
106      * @parameter expression="${htmlDirectory}" default-value="${project.build.directory}/site/services/dictionarydocs"
107      */
108     private File htmlDirectory;
109 
110 	private String testDictionaryFile;
111 
112 	private LinkedHashMap<String, String> dictionaryFileToMessageStructureMap  = new LinkedHashMap<String, String>();
113 
114 	
115     public List<String> getSourceDirs() {
116 		return sourceDirs;
117 	}
118 
119 	public void setSourceDirs(List<String> sourceDirs) {
120 		this.sourceDirs = sourceDirs;
121 	}
122 
123 	public void setHtmlDirectory(File htmlDirectory) {
124         this.htmlDirectory = htmlDirectory;
125     }
126 
127     public File getHtmlDirectory() {
128         return htmlDirectory;
129     }
130 
131     public MavenProject getProject() {
132         return project;
133     }
134 
135     public List<String> getSupportFiles() {
136         return supportFiles;
137     }
138 
139     public void setSupportFiles(List<String> supportFiles) {
140     	this.supportFiles.clear();
141     	
142     	if (supportFiles != null)
143     		this.supportFiles.addAll(supportFiles);
144     }
145 
146     private ServiceContractModel getModel() {
147         ServiceContractModel instance = new ServiceContractModelQDoxLoader(
148                 sourceDirs);
149         return new ServiceContractModelCache(instance);
150     }
151 
152     private boolean validate(ServiceContractModel model) {
153         Collection<String> errors = new ServiceContractModelValidator(model).validate();
154         if (errors.size() > 0) {
155             StringBuilder buf = new StringBuilder();
156             buf.append(errors.size()).append(" errors found while validating the data.");
157             return false;
158         }
159         return true;
160     }
161     
162     @Override
163     public void execute()
164             throws MojoExecutionException {
165     	this.getLog().info("generating dictionary documentation");
166         
167         if (getPluginContext() != null) {
168         	project = (MavenProject) getPluginContext().get("project");
169         }
170         
171         // add the current projects classpath to the plugin so the springbean
172         // loader can find the xml files and lasses that it needs to can be run
173         // against the current project's files
174         if (project != null) {
175             this.getLog().info("adding current project's classpath to plugin class loader");
176             List<String> runtimeClasspathElements;
177             try {
178                 runtimeClasspathElements = project.getRuntimeClasspathElements();
179             } catch (DependencyResolutionRequiredException ex) {
180                 throw new MojoExecutionException("Failed to get runtime classpath elements.", ex);
181             }
182             URL[] runtimeUrls = new URL[runtimeClasspathElements.size()];
183             for (int i = 0; i < runtimeClasspathElements.size(); i++) {
184                 String element = (String) runtimeClasspathElements.get(i);
185                 try {
186                     runtimeUrls[i] = new File(element).toURI().toURL();
187                 } catch (MalformedURLException ex) {
188                     throw new MojoExecutionException(element, ex);
189                 }
190             }
191             URLClassLoader newLoader = new URLClassLoader(runtimeUrls,
192                     Thread.currentThread().getContextClassLoader());
193             Thread.currentThread().setContextClassLoader(newLoader);
194         }
195 
196 
197         if (!htmlDirectory.exists()) {
198             if (!htmlDirectory.mkdirs()) {
199                 throw new IllegalArgumentException("Could not create directory "
200                         + this.htmlDirectory.getPath());
201             }
202         }
203         
204         Set<String> inpFiles = new LinkedHashSet<String>();
205         if (project != null) {
206         ServiceContractModel model = this.getModel();
207         this.validate(model);
208         inpFiles.addAll(extractDictionaryFiles(model));
209 
210     }
211         else {
212         		inpFiles.add(this.testDictionaryFile);
213         }
214     
215 
216         String outputDir = this.htmlDirectory.getAbsolutePath();
217         DictionaryTesterHelper tester = new DictionaryTesterHelper(outputDir, inpFiles, this.supportFiles);
218         tester.doTest(project.getVersion(), DateUtility.asYMDHMInEasternTimeZone(new DateTime()));
219 
220         // write out the index file
221         String indexFileName = this.htmlDirectory.getPath() + "/" + "index.html";
222         File indexFile = new File(indexFileName);
223         OutputStream outputStream;
224         try {
225             outputStream = new FileOutputStream(indexFile, false);
226         } catch (FileNotFoundException ex) {
227 //            throw new MojoExecutionException(indexFileName, ex);
228             throw new IllegalArgumentException(indexFileName, ex);
229         }
230         
231         String formattedDate = DateUtility.asYMDHMInEasternTimeZone(new DateTime());
232         
233         PrintStream out = new PrintStream(outputStream);
234         
235         DictionaryFormatter.writeHeader(out, "Data Dictionary Index");
236         
237         VersionLinesUtility.writeVersionTag(out, "<a href=\"index.html\">Home</a>", "<a href=\"../contractdocs/index.html\">Contract Docs Home</a>", project.getVersion(), formattedDate);
238         
239         out.println("<h1>Data Dictionary Index</h1>");
240         out.println("<blockquote>A Red background indicates that there is a problem with the data dictionary for that type.</blockquote>");
241         out.println("<ul>");
242         
243         Map<String, List<String>> fileToBeanNameMap = tester.getInputFileToBeanNameMap();
244         
245 		for (String inputFile : fileToBeanNameMap.keySet()) {
246 
247 			boolean containsError = false;
248 			
249 			if (tester.getInvalidDictionaryFiles().contains(inputFile)) {
250 				containsError = true;
251 			}
252 			
253 			List<String> beanIds = fileToBeanNameMap.get(inputFile);
254 
255 			for (String beanId : beanIds) {
256 
257 				String outputFileName = beanId + ".html";
258 				
259 				if (containsError)
260 					out.println ("<li class=\"invalid\">");
261 				else
262 					out.println ("<li>");
263 				
264 				out.println("<a href=\"" + outputFileName + "\">" + beanId
265 						+ "</a>");
266 			}
267 		}
268         out.println("</ul>");
269         
270 		
271         
272 		if (tester.getMissingDictionaryFiles().size() > 0) {
273 			out.println("<h1>Missing Dictionary Files</h1>");
274 			out.println("<blockquote>The Message structure exists but there is no dictionary file present.</blockquote>");
275 			out.println("<ul>");
276 			for (String missingFile : tester.getMissingDictionaryFiles()) {
277 				out.println("<li><b>" + missingFile + "</b></li>");
278 			}
279 			out.println("</ul>");
280 
281 		}
282         
283         DictionaryFormatter.writeFooter(out);
284         out.close();
285         
286         log.info("finished generating dictionary documentation");
287     }
288 
289 	private Collection<String> extractDictionaryFiles(
290 			ServiceContractModel model) {
291 		
292 		Set<String> dictionaryFiles = new LinkedHashSet<String>();
293 		
294 		List<MessageStructure> ms = model.getMessageStructures();
295 		
296 		for (MessageStructure messageStructure : ms) {
297 			
298 			String inputFileName = "ks-" + messageStructure.getXmlObject() + "-dictionary.xml";
299 
300 			dictionaryFiles.add(inputFileName);
301 			
302 			// we also track the file name to message structure so we can link the invalid
303 			dictionaryFileToMessageStructureMap.put(inputFileName, messageStructure.getXmlObject());
304 			
305 		}
306 		
307 		
308 		return dictionaryFiles;
309 	}
310 
311 	/**
312 	 * Used for testing to hard code a single dictionary file to use.
313 	 * @param string
314 	 */
315 	public void setTestDictionaryFile(String dictionaryFile) {
316 		this.testDictionaryFile = dictionaryFile;
317 		
318 	}
319 }