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.Iterator;
29  import java.util.LinkedHashMap;
30  import java.util.LinkedHashSet;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  
35  import org.apache.maven.artifact.DependencyResolutionRequiredException;
36  import org.apache.maven.plugin.AbstractMojo;
37  import org.apache.maven.plugin.MojoExecutionException;
38  import org.apache.maven.project.MavenProject;
39  import org.joda.time.DateTime;
40  import org.kuali.student.common.mojo.AbstractKSMojo;
41  import org.kuali.student.contract.model.MessageStructure;
42  import org.kuali.student.contract.model.Service;
43  import org.kuali.student.contract.model.ServiceContractModel;
44  import org.kuali.student.contract.model.impl.ServiceContractModelCache;
45  import org.kuali.student.contract.model.impl.ServiceContractModelQDoxLoader;
46  import org.kuali.student.contract.model.util.DateUtility;
47  import org.kuali.student.contract.model.util.VersionLinesUtility;
48  import org.kuali.student.contract.model.validation.ServiceContractModelValidator;
49  import org.kuali.student.datadictionary.util.DictionaryFormatter;
50  import org.kuali.student.datadictionary.util.DictionaryTesterHelper;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  /**
55   * Mojo for generating a formatted view of the data dictionary.
56   * 
57   * <pre>
58   * {@code
59   * <plugin>
60   * 		<groupId>org.kuali.maven.plugins</groupId>
61   *      <artifactId>maven-kscontractdoc-plugin</artifactId>
62   *      <execution>
63   *      	<id>generate-dictionary-documentation</id>
64   *          <phase>site</phase>
65   *          <goals>
66   *          	<goal>ksdictionarydoc</goal>                            
67   *          </goals>
68   *          <configuration>
69   *           <supportFiles>
70   *           	<supportFile>commonApplicationContext.xml</supportFile>
71   *           </supportFiles>
72   *          </configuration>
73   *     </execution>
74   * </plugin>
75   *  }
76   * </pre>
77   * 
78   * 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 
79   * file plus any files specified using the <supportFile> configuration parameter.
80   * 
81   *  Errors with an application context are detected and logged but will not break the plugin's ability to generate the other files.
82   * 
83   * @goal ksdictionarydoc
84   * @phase site
85   * @requiresDependencyResolution test
86   */
87  public class KSDictionaryDocMojo extends AbstractKSMojo {
88  
89  	private static final Logger log = LoggerFactory.getLogger(KSDictionaryDocMojo.class);
90  	
91      /**
92       * @parameter expression="${project}"
93       * @required
94       * @readonly
95       */
96      private MavenProject project;
97      
98     
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 	public void setHtmlDirectory(File htmlDirectory) {
115         this.htmlDirectory = htmlDirectory;
116     }
117 
118     public File getHtmlDirectory() {
119         return htmlDirectory;
120     }
121 
122     public MavenProject getProject() {
123         return project;
124     }
125 
126     public List<String> getSupportFiles() {
127         return supportFiles;
128     }
129 
130     public void setSupportFiles(List<String> supportFiles) {
131     	this.supportFiles.clear();
132     	
133     	if (supportFiles != null)
134     		this.supportFiles.addAll(supportFiles);
135     }
136     
137     @Override
138     public void execute()
139             throws MojoExecutionException {
140     	this.getLog().info("generating dictionary documentation");
141         
142         if (getPluginContext() != null) {
143         	project = (MavenProject) getPluginContext().get("project");
144         }
145         
146         // add the current projects classpath to the plugin so the springbean
147         // loader can find the xml files and lasses that it needs to can be run
148         // against the current project's files
149         if (project != null) {
150             this.getLog().info("adding current project's classpath to plugin class loader");
151             List<String> runtimeClasspathElements;
152             try {
153                 runtimeClasspathElements = project.getRuntimeClasspathElements();
154             } catch (DependencyResolutionRequiredException ex) {
155                 throw new MojoExecutionException("Failed to get runtime classpath elements.", ex);
156             }
157             URL[] runtimeUrls = new URL[runtimeClasspathElements.size()];
158             for (int i = 0; i < runtimeClasspathElements.size(); i++) {
159                 String element = (String) runtimeClasspathElements.get(i);
160                 try {
161                     runtimeUrls[i] = new File(element).toURI().toURL();
162                 } catch (MalformedURLException ex) {
163                     throw new MojoExecutionException(element, ex);
164                 }
165             }
166             URLClassLoader newLoader = new URLClassLoader(runtimeUrls,
167                     Thread.currentThread().getContextClassLoader());
168             Thread.currentThread().setContextClassLoader(newLoader);
169         }
170 
171 
172         if (!htmlDirectory.exists()) {
173             if (!htmlDirectory.mkdirs()) {
174                 throw new IllegalArgumentException("Could not create directory "
175                         + this.htmlDirectory.getPath());
176             }
177         }
178         
179         Set<String> inpFiles = new LinkedHashSet<String>();
180 		if (project != null) {
181 			ServiceContractModel model = this.getModel();
182 			this.validate(model);
183 			inpFiles.addAll(extractDictionaryFiles(model));
184 
185 		} else {
186 			inpFiles.add(this.testDictionaryFile);
187 		}
188     
189 
190         String outputDir = this.htmlDirectory.getAbsolutePath();
191         DictionaryTesterHelper tester = new DictionaryTesterHelper(outputDir, inpFiles, this.supportFiles);
192         tester.doTest(project.getVersion(), DateUtility.asYMDHMInEasternTimeZone(new DateTime()));
193 
194         // write out the index file
195         String indexFileName = this.htmlDirectory.getPath() + "/" + "index.html";
196         File indexFile = new File(indexFileName);
197         OutputStream outputStream;
198         try {
199             outputStream = new FileOutputStream(indexFile, false);
200         } catch (FileNotFoundException ex) {
201 //            throw new MojoExecutionException(indexFileName, ex);
202             throw new IllegalArgumentException(indexFileName, ex);
203         }
204         
205         String formattedDate = DateUtility.asYMDHMInEasternTimeZone(new DateTime());
206         
207         PrintStream out = new PrintStream(outputStream);
208         
209         DictionaryFormatter.writeHeader(out, "Data Dictionary Index");
210         
211         VersionLinesUtility.writeVersionTag(out, "<a href=\"index.html\">Home</a>", "<a href=\"../contractdocs/index.html\">Contract Docs Home</a>", project.getVersion(), formattedDate);
212         
213         out.println("<h1>Data Dictionary Index</h1>");
214         out.println("<blockquote>A Red background indicates that there is a problem with the data dictionary for that type.</blockquote>");
215         out.println("<ul>");
216         
217         Map<String, List<String>> fileToBeanNameMap = tester.getInputFileToBeanNameMap();
218         
219 		for (String inputFile : fileToBeanNameMap.keySet()) {
220 
221 			boolean containsError = false;
222 			
223 			if (tester.getInvalidDictionaryFiles().contains(inputFile)) {
224 				containsError = true;
225 			}
226 			
227 			List<String> beanIds = fileToBeanNameMap.get(inputFile);
228 
229 			for (String beanId : beanIds) {
230 
231 				String outputFileName = beanId + ".html";
232 				
233 				if (containsError)
234 					out.println ("<li class=\"invalid\">");
235 				else
236 					out.println ("<li>");
237 				
238 				out.println("<a href=\"" + outputFileName + "\">" + beanId
239 						+ "</a>");
240 			}
241 		}
242         out.println("</ul>");
243         
244 		
245         
246 		if (tester.getMissingDictionaryFiles().size() > 0) {
247 			out.println("<h1>Missing Dictionary Files</h1>");
248 			out.println("<blockquote>The Message structure exists but there is no dictionary file present.</blockquote>");
249 			out.println("<ul>");
250 			for (String missingFile : tester.getMissingDictionaryFiles()) {
251 				out.println("<li><b>" + missingFile + "</b></li>");
252 			}
253 			out.println("</ul>");
254 
255 		}
256         
257         DictionaryFormatter.writeFooter(out);
258         out.flush();
259         out.close();
260         
261         log.info("finished generating dictionary documentation");
262     }
263 
264 	private Collection<String> extractDictionaryFiles(
265 			ServiceContractModel model) {
266 		
267 		Set<String> dictionaryFiles = new LinkedHashSet<String>();
268 		
269 		List<MessageStructure> mss = new ArrayList<MessageStructure>(model.getMessageStructures());
270 		
271 		// this is needed to remove r1 duplicates
272 		// we only seem to hold the r2 data but the entry exists multiple times.
273 		Set<String>mergedMessageStructureNames = new LinkedHashSet<String>();
274         
275         Iterator<MessageStructure> it = mss.iterator();
276         
277         while (it.hasNext()) {
278         	MessageStructure ms = it.next();
279 			
280         	String messageStructureName = ms.getXmlObject();
281         	
282         	if (mergedMessageStructureNames.contains(messageStructureName) || !messageStructureName.endsWith("Info")) {
283         		
284         		// remove duplicates
285         		// remove classes that don't end in Info
286         		it.remove();
287         		
288         	}
289         	else
290         		mergedMessageStructureNames.add(ms.getName());
291 		}
292 		
293 		for (MessageStructure messageStructure : mss) {
294 			
295 			String inputFileName = "ks-" + messageStructure.getXmlObject() + "-dictionary.xml";
296 
297 			dictionaryFiles.add(inputFileName);
298 			
299 			// we also track the file name to message structure so we can link the invalid
300 			dictionaryFileToMessageStructureMap.put(inputFileName, messageStructure.getXmlObject());
301 			
302 		}
303 		
304 		
305 		return dictionaryFiles;
306 	}
307 
308 	/**
309 	 * Used for testing to hard code a single dictionary file to use.
310 	 * @param string
311 	 */
312 	public void setTestDictionaryFile(String dictionaryFile) {
313 		this.testDictionaryFile = dictionaryFile;
314 		
315 	}
316 }