View Javadoc

1   /*
2    * EL4J, the Extension Library for the J2EE, adds incremental enhancements to
3    * the spring framework, http://el4j.sf.net
4    * Copyright (C) 2008 by ELCA Informatique SA, Av. de la Harpe 22-24,
5    * 1000 Lausanne, Switzerland, http://www.elca.ch
6    *
7    * EL4J is published under the GNU Lesser General Public License (LGPL)
8    * Version 2.1. See http://www.gnu.org/licenses/
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13   * GNU Lesser General Public License for more details.
14   *
15   * For alternative licensing, please contact info@elca.ch
16   */
17  package ch.elca.el4j.coberturaruntime;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.text.SimpleDateFormat;
22  import java.util.ArrayList;
23  import java.util.Date;
24  import java.util.List;
25  
26  import net.sourceforge.cobertura.coveragedata.CoverageDataFileHandler;
27  import net.sourceforge.cobertura.coveragedata.ProjectData;
28  import net.sourceforge.cobertura.reporting.Main;
29  import net.sourceforge.cobertura.util.ConfigurationUtil;
30  
31  import org.codehaus.plexus.util.FileUtils;
32  import org.springframework.util.FileCopyUtils;
33  import org.springframework.util.StringUtils;
34  
35  import ch.elca.el4j.util.codingsupport.annotations.FindBugsSuppressWarnings;
36  
37  /**
38   * Bean to control the cobertura runtime behavior.
39   *
40   * @svnLink $Revision: 4010 $;$Date: 2009-12-01 10:59:54 +0100 (Di, 01. Dez 2009) $;$Author: jonasha $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/cobertura-runtime/src/main/java/ch/elca/el4j/coberturaruntime/CoberturaRuntimeControllerImpl.java $
41   *
42   * @author Martin Zeltner (MZE)
43   */
44  public class CoberturaRuntimeControllerImpl implements CoberturaRuntimeController {
45  	/**
46  	 * Property key for the cobertura data directory.
47  	 */
48  	public static final String COBERTURA_RUNTIME_DATA_DIRECTORY = "cobertura-runtime.dataDirectory";
49  	
50  	/**
51  	 * Property key for the cobertura data filename.
52  	 */
53  	public static final String COBERTURA_RUNTIME_DATA_FILENAME = "cobertura-runtime.dataFilename";
54  	
55  	/**
56  	 * Property key for the cobertura data directory.
57  	 */
58  	public static final String COBERTURA_RUNTIME_SRC_COL_DIR_NAME = "cobertura-runtime.sourceCollectorDirectoryName";
59  	
60  	/**
61  	 * Property key for the cobertura keep reports flag.
62  	 */
63  	public static final String COBERTURA_RUNTIME_KEEP_REPORTS = "cobertura-runtime.keepReports";
64  	
65  	/**
66  	 * Is the cobertura data directory where all info will be saved.
67  	 */
68  	protected final File m_coberturaDataDirectory;
69  	
70  	/**
71  	 * Is the cobertura data filename.
72  	 */
73  	protected final String m_coberturaDataFilename;
74  	
75  	/**
76  	 * Is the directory where the collected sources are.
77  	 */
78  	protected final File m_coberturaCollectedSourcesDirectory;
79  	
80  	/**
81  	 * If <code>true</code> every report will have its own directory.
82  	 */
83  	protected final boolean m_keepReports;
84  
85  	/**
86  	 * If <code>false</code> the controller is running in online mode.
87  	 */
88  	protected final boolean m_isOfflineMode;
89  	
90  	/**
91  	 * If <code>false</code> then cobertura is not recording data. Default is <code>true</code>.
92  	 */
93  	private boolean m_isRecordingData = true;
94  	
95  	/**
96  	 * Is the data file where cobertura data will be saved.
97  	 */
98  	private File m_coberturaDataFile;
99  	
100 	/**
101 	 * Constructor to init the cobertura data directory in online mode.
102 	 */
103 	public CoberturaRuntimeControllerImpl() {
104 		this(false);
105 	}
106 	
107 	/**
108 	 * Constructor to init the cobertura data directory.
109 	 * 
110 	 * @param offlineMode <code>true</code> if no app is running.
111 	 */
112 	public CoberturaRuntimeControllerImpl(boolean offlineMode) {
113 		ConfigurationUtil config = new ConfigurationUtil();
114 		
115 		// Load the data dir path
116 		String path = config.getProperty(COBERTURA_RUNTIME_DATA_DIRECTORY, null);
117 		if (!StringUtils.hasText(path)) {
118 			throw new RuntimeException("Property '" + COBERTURA_RUNTIME_DATA_DIRECTORY + "' is not defined! "
119 				+ "It should be defined in file 'cobertura.properties'!");
120 		}
121 		m_coberturaDataDirectory = new File(path);
122 		
123 		// Load the data filename
124 		String dataFilename = config.getProperty(COBERTURA_RUNTIME_DATA_FILENAME, null);
125 		if (!StringUtils.hasText(dataFilename)) {
126 			throw new RuntimeException("Property '" + COBERTURA_RUNTIME_DATA_FILENAME + "' is not defined! "
127 				+ "It should be defined in file 'cobertura.properties'!");
128 		}
129 		m_coberturaDataFilename = dataFilename;
130 		
131 		// Load the sourceCollectorDirectoryName
132 		String sourceDir = config.getProperty(COBERTURA_RUNTIME_SRC_COL_DIR_NAME, null);
133 		if (!StringUtils.hasText(sourceDir)) {
134 			throw new RuntimeException("Property '" + COBERTURA_RUNTIME_SRC_COL_DIR_NAME + "' is not defined! "
135 				+ "It should be defined in file 'cobertura.properties'!");
136 		}
137 		m_coberturaCollectedSourcesDirectory = new File(m_coberturaDataDirectory, sourceDir);
138 		
139 		// Load the keepReports
140 		String keepReportsString = config.getProperty(COBERTURA_RUNTIME_KEEP_REPORTS, null);
141 		if (!StringUtils.hasText(keepReportsString)) {
142 			throw new RuntimeException("Property '" + COBERTURA_RUNTIME_KEEP_REPORTS
143 				+ "' (boolean value) is not defined! "
144 				+ "It should be defined in file 'cobertura.properties'!");
145 		}
146 		m_keepReports = Boolean.parseBoolean(keepReportsString);
147 		
148 		// Set the cobertura data file
149 		m_isOfflineMode = offlineMode;
150 		m_coberturaDataFile = new File(m_coberturaDataDirectory, m_coberturaDataFilename);
151 		if (m_isOfflineMode) {
152 			m_isRecordingData = false;
153 			CoverageDataFileHandler.loadCoverageData(m_coberturaDataFile);
154 		} else {
155 			File defaultDataFile = CoverageDataFileHandler.getDefaultDataFile();
156 			if (!defaultDataFile.equals(m_coberturaDataFile)) {
157 				throw new RuntimeException("The configured '" + m_coberturaDataFile.getAbsolutePath() 
158 					+ "' and the already set cobertura data file path '" + defaultDataFile.getAbsolutePath()
159 					+ "' are not the same!");
160 			}
161 		}
162 		
163 	}
164 	
165 	/** {@inheritDoc} */
166 	public synchronized boolean startRecording() {
167 		if (m_isOfflineMode || m_isRecordingData) {
168 			return false;
169 		}
170 		
171 		flushRecords();
172 		try {
173 			Thread.sleep(1000);
174 			ProjectData coverageData = CoverageDataFileHandler.loadCoverageData(m_coberturaDataFile);
175 			setGlobalCoverageDataAndDataFile(coverageData, m_coberturaDataFile);
176 		} catch (Exception e) {
177 			throw new RuntimeException("Exception while trying to start recording!", e);
178 		}
179 		
180 		m_isRecordingData = true;
181 		return true;
182 	}
183 	
184 	/** {@inheritDoc} */
185 	@FindBugsSuppressWarnings(value = "REC_CATCH_EXCEPTION",
186 					justification = "Don't care which exception occurs, will always throw a Runtime exception.")	
187 	public synchronized boolean stopRecording() {
188 		if (m_isOfflineMode || !m_isRecordingData) {
189 			return false;
190 		}
191 		
192 		flushRecords();
193 		try {
194 			Thread.sleep(1000);
195 			File tempDataFile = File.createTempFile("cobertura-temp-", ".ser");
196 			FileCopyUtils.copy(m_coberturaDataFile, tempDataFile);
197 			tempDataFile.deleteOnExit();
198 			ProjectData coverageData = CoverageDataFileHandler.loadCoverageData(tempDataFile);
199 			setGlobalCoverageDataAndDataFile(coverageData, tempDataFile);
200 		} catch (Exception e) {
201 			throw new RuntimeException("Exception while trying to stop recording!", e);
202 		}
203 		
204 		m_isRecordingData = false;
205 		return true;
206 	}
207 	
208 	/**
209 	 * Sets the global coverage data and the data file via reflection, due this point is really 
210 	 * poorly made in cobertura.
211 	 * 
212 	 * @param coverageData Is the loaded cobertura data.
213 	 * @param coberturaDataFile Is the file reference of the cobertura data.
214 	 */
215 	protected synchronized void setGlobalCoverageDataAndDataFile(ProjectData coverageData, File coberturaDataFile) {
216 //		try {
217 //			Method globalProjectDataSetter = ProjectData.class.getDeclaredMethod(
218 //				"setGlobalProjectData", ProjectData.class);
219 //			globalProjectDataSetter.invoke(null, coverageData);
220 //		} catch (Exception e) {
221 //			e.printStackTrace();
222 //			throw new RuntimeException("Exception while trying to set the global coverage data!", e);
223 //		}
224 		ProjectData.setGlobalProjectData(coverageData);
225 		
226 //		try {
227 //			Method defaultDataFileSetter = CoverageDataFileHandler.class.getDeclaredMethod(
228 //				"setDefaultDataFile", File.class);
229 //			defaultDataFileSetter.invoke(null, coberturaDataFile);
230 //		} catch (Exception e) {
231 //			e.printStackTrace();
232 //			throw new RuntimeException("Exception while trying to set the default coverage data file!", e);
233 //		}
234 		CoverageDataFileHandler.setDefaultDataFile(coberturaDataFile);
235 	}
236 
237 	/** {@inheritDoc} */
238 	public synchronized boolean isRecording() {
239 		if (m_isOfflineMode) {
240 			return false;
241 		}
242 		return m_isRecordingData;
243 	}
244 	
245 	/** {@inheritDoc} */
246 	public synchronized void flushRecords() {
247 		if (m_isOfflineMode) {
248 			return;
249 		}
250 		ProjectData.saveGlobalProjectData();
251 	}
252 
253 	/** {@inheritDoc} */
254 	public synchronized String getDataFilePath() {
255 		return m_coberturaDataFile != null ? m_coberturaDataFile.getAbsolutePath() : "UNKNOWN!";
256 	}
257 
258 	/** {@inheritDoc} */
259 	public synchronized String generateReport() {
260 		// First flush the records
261 		flushRecords();
262 		
263 		// Create timestamp string
264 		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HHmmss");
265 		String timestampString = sdf.format(new Date());
266 		
267 		// Create a new directory for the new report
268 		// If the dir already exists delete it first
269 		String reportDirectoryName;
270 		if (m_keepReports) {
271 			reportDirectoryName = "report-" + timestampString;
272 		} else {
273 			reportDirectoryName = "report";
274 		}
275 		File reportDirectory = new File(m_coberturaDataDirectory, reportDirectoryName);
276 		String reportDirectoryPath = reportDirectory.getAbsolutePath();
277 		if (reportDirectory.exists()) {
278 			try {
279 				FileUtils.deleteDirectory(reportDirectory);
280 			} catch (IOException e) {
281 				throw new RuntimeException("Could not delete the old report directory '" 
282 															+ reportDirectoryPath + "'!", e);
283 			}
284 		}
285 		if (!reportDirectory.mkdirs()) {
286 			throw new RuntimeException("Could not create new report directory");
287 		}	
288 		
289 		// Copy the data file to the report dir
290 		String newDataFileName = "cobertura-" + timestampString + ".ser";
291 		File dataFile = new File(reportDirectory, newDataFileName);
292 		try {
293 			FileCopyUtils.copy(m_coberturaDataFile, dataFile);
294 		} catch (IOException e) {
295 			throw new RuntimeException("Could not copy the cobertura data file!", e);
296 		}
297 		String dataFilePath = dataFile.getAbsolutePath();
298 		
299 		// Generate the report
300 		List<String> arguments = new ArrayList<String>();
301 		arguments.add("--datafile");
302 		arguments.add(dataFilePath);
303 		arguments.add("--destination");
304 		arguments.add(reportDirectoryPath);
305 		arguments.add(m_coberturaCollectedSourcesDirectory.getAbsolutePath());
306 		try {
307 			Main.main(arguments.toArray(new String[arguments.size()]));
308 		} catch (Exception e) {
309 			throw new RuntimeException("Exception while generating the report!", e);
310 		}
311 		
312 		return reportDirectoryPath;
313 	}
314 }