001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2015 ForgeRock AS. 025 */ 026package org.forgerock.maven; 027 028import static org.forgerock.util.Utils.*; 029 030import java.io.BufferedReader; 031import java.io.File; 032import java.io.FileReader; 033import java.io.IOException; 034import java.util.Arrays; 035import java.util.Calendar; 036import java.util.LinkedList; 037import java.util.List; 038 039import org.apache.maven.plugin.AbstractMojo; 040import org.apache.maven.plugin.MojoExecutionException; 041import org.apache.maven.plugin.MojoFailureException; 042import org.apache.maven.plugins.annotations.Parameter; 043import org.apache.maven.project.MavenProject; 044import org.apache.maven.scm.ScmException; 045import org.apache.maven.scm.ScmFile; 046import org.apache.maven.scm.ScmFileSet; 047import org.apache.maven.scm.ScmFileStatus; 048import org.apache.maven.scm.command.status.StatusScmResult; 049import org.apache.maven.scm.manager.BasicScmManager; 050import org.apache.maven.scm.manager.NoSuchScmProviderException; 051import org.apache.maven.scm.manager.ScmManager; 052import org.apache.maven.scm.provider.ScmProvider; 053import org.apache.maven.scm.provider.git.gitexe.GitExeScmProvider; 054import org.apache.maven.scm.provider.svn.svnexe.SvnExeScmProvider; 055import org.apache.maven.scm.repository.ScmRepository; 056import org.apache.maven.scm.repository.ScmRepositoryException; 057 058/** 059 * Abstract class which is used for both copyright checks and updates. 060 */ 061public abstract class CopyrightAbstractMojo extends AbstractMojo { 062 063 /** The Maven Project. */ 064 @Parameter(required = true, property = "project", readonly = true) 065 private MavenProject project; 066 067 /** 068 * Copyright owner. 069 * This string token must be present on the same line with 'copyright' keyword and the current year. 070 */ 071 @Parameter(required = true, defaultValue = "ForgeRock AS") 072 private String copyrightOwnerToken; 073 074 /** The path to the root of the Subversion workspace to check. */ 075 @Parameter(required = true, defaultValue = "${basedir}") 076 private String scmWorkspaceRoot; 077 078 @Parameter(required = true, defaultValue = "${project.scm.connection}") 079 private String scmRepositoryUrl; 080 081 /** The file extensions to test. */ 082 public static final List<String> CHECKED_EXTENSIONS = new LinkedList<>(Arrays.asList( 083 "bat", "c", "h", "html", "java", "ldif", "Makefile", "mc", "sh", "txt", "xml", "xsd", "xsl")); 084 085 private static final List<String> EXCLUDED_END_COMMENT_BLOCK_TOKEN = new LinkedList<>(Arrays.asList( 086 "*/", "-->")); 087 088 private static final List<String> SUPPORTED_COMMENT_MIDDLE_BLOCK_TOKEN = new LinkedList<>(Arrays.asList( 089 "*", "#", "rem", "!")); 090 091 private static final List<String> SUPPORTED_START_BLOCK_COMMENT_TOKEN = new LinkedList<>(Arrays.asList( 092 "/*", "<!--")); 093 094 /** The string representation of the current year. */ 095 Integer currentYear = Calendar.getInstance().get(Calendar.YEAR); 096 097 private final List<String> incorrectCopyrightFilePaths = new LinkedList<>(); 098 099 /** The overall SCM Client Manager. */ 100 private ScmManager scmManager; 101 102 private ScmRepository scmRepository; 103 104 List<String> getIncorrectCopyrightFilePaths() { 105 return incorrectCopyrightFilePaths; 106 } 107 108 private ScmManager getScmManager() throws MojoExecutionException { 109 if (scmManager == null) { 110 scmManager = new BasicScmManager(); 111 String scmProviderID = getScmProviderID(); 112 ScmProvider scmProvider; 113 if ("svn".equals(scmProviderID)) { 114 scmProvider = new SvnExeScmProvider(); 115 } else if ("git".equals(scmProviderID)) { 116 scmProvider = new GitExeScmProvider(); 117 } else { 118 throw new MojoExecutionException("Unsupported scm provider: " + scmProviderID + " or " 119 + getIncorrectScmRepositoryUrlMsg()); 120 } 121 scmManager.setScmProvider(scmProviderID, scmProvider); 122 } 123 124 return scmManager; 125 } 126 127 private String getScmProviderID() throws MojoExecutionException { 128 try { 129 return scmRepositoryUrl.split(":")[1]; 130 } catch (Exception e) { 131 throw new MojoExecutionException(getIncorrectScmRepositoryUrlMsg(), e); 132 } 133 } 134 135 String getIncorrectScmRepositoryUrlMsg() { 136 return "the scmRepositoryUrl property with value '" + scmRepositoryUrl + "' is incorrect. " 137 + "The URL has to respect the format: scm:[provider]:[provider_specific_url]"; 138 } 139 140 ScmRepository getScmRepository() throws MojoExecutionException { 141 if (scmRepository == null) { 142 try { 143 scmRepository = getScmManager().makeScmRepository(scmRepositoryUrl); 144 } catch (NoSuchScmProviderException e) { 145 throw new MojoExecutionException("Could not find a provider.", e); 146 } catch (ScmRepositoryException e) { 147 throw new MojoExecutionException("Error while connecting to the repository", e); 148 } 149 } 150 151 return scmRepository; 152 } 153 154 String getScmWorkspaceRoot() { 155 return scmWorkspaceRoot; 156 } 157 158 /** Performs a diff with current working directory state against remote HEAD revision. */ 159 List<String> getChangedFiles() throws MojoExecutionException, MojoFailureException { 160 try { 161 ScmFileSet workspaceFileSet = new ScmFileSet(new File(getScmWorkspaceRoot())); 162 StatusScmResult statusResult = getScmManager().status(getScmRepository(), workspaceFileSet); 163 if (!statusResult.isSuccess()) { 164 getLog().error("Impossible to perform scm status command because " + statusResult.getCommandOutput()); 165 throw new MojoFailureException("SCM error"); 166 } 167 168 List<ScmFile> scmFiles = statusResult.getChangedFiles(); 169 List<String> changedFilePaths = new LinkedList<>(); 170 for (ScmFile scmFile : scmFiles) { 171 if (scmFile.getStatus() != ScmFileStatus.UNKNOWN) { 172 changedFilePaths.add(scmFile.getPath()); 173 } 174 } 175 176 return changedFilePaths; 177 } catch (ScmException e) { 178 throw new MojoExecutionException("Encountered an error while examining modified files, SCM status: " 179 + e.getMessage() + "No further checks will be performed.", e); 180 } 181 } 182 183 /** Examines the provided files list to determine whether each changed file copyright is acceptable. */ 184 void checkCopyrights() throws MojoExecutionException, MojoFailureException { 185 for (String changedFileName : getChangedFiles()) { 186 File changedFile = new File(getScmWorkspaceRoot(), changedFileName); 187 if (!changedFile.exists() || !changedFile.isFile()) { 188 continue; 189 } 190 191 int lastPeriodPos = changedFileName.lastIndexOf('.'); 192 if (lastPeriodPos > 0) { 193 String extension = changedFileName.substring(lastPeriodPos + 1); 194 if (!CHECKED_EXTENSIONS.contains(extension.toLowerCase())) { 195 continue; 196 } 197 } else if (fileNameEquals("bin", changedFile.getParentFile()) 198 && fileNameEquals("resource", changedFile.getParentFile().getParentFile())) { 199 // ignore resource/bin directory. 200 continue; 201 } 202 203 if (!checkCopyrightForFile(changedFile)) { 204 incorrectCopyrightFilePaths.add(changedFile.getAbsolutePath()); 205 } 206 } 207 } 208 209 private boolean fileNameEquals(String folderName, File file) { 210 return file != null && folderName.equals(file.getName()); 211 } 212 213 /** 214 * Check to see whether the provided file has a comment line containing a 215 * copyright without the current year. 216 */ 217 @SuppressWarnings("resource") 218 private boolean checkCopyrightForFile(File changedFile) throws MojoExecutionException { 219 BufferedReader reader = null; 220 try { 221 reader = new BufferedReader(new FileReader(changedFile)); 222 String line; 223 while ((line = reader.readLine()) != null) { 224 String lowerLine = line.toLowerCase().trim(); 225 if (isCommentLine(lowerLine) 226 && lowerLine.contains("copyright") 227 && line.contains(currentYear.toString()) 228 && line.contains(copyrightOwnerToken)) { 229 reader.close(); 230 return true; 231 } 232 } 233 234 return false; 235 } catch (IOException ioe) { 236 throw new MojoExecutionException("Could not read file " + changedFile.getPath() 237 + " to check copyright date. No further copyright date checking will be performed."); 238 } finally { 239 closeSilently(reader); 240 } 241 } 242 243 private String getCommentToken(String line, boolean includesStartBlock) { 244 List<String> supportedTokens = SUPPORTED_COMMENT_MIDDLE_BLOCK_TOKEN; 245 if (includesStartBlock) { 246 supportedTokens.addAll(SUPPORTED_START_BLOCK_COMMENT_TOKEN); 247 } 248 249 if (trimmedLineStartsWith(line, EXCLUDED_END_COMMENT_BLOCK_TOKEN) != null) { 250 return null; 251 } 252 253 return trimmedLineStartsWith(line, supportedTokens); 254 } 255 256 private String trimmedLineStartsWith(String line, List<String> supportedTokens) { 257 for (String token : supportedTokens) { 258 if (line.trim().startsWith(token)) { 259 return token; 260 } 261 } 262 return null; 263 } 264 265 boolean isNonEmptyCommentedLine(String line) { 266 String commentToken = getCommentTokenInBlock(line); 267 return commentToken == null || !commentToken.equals(line.trim()); 268 } 269 270 String getCommentTokenInBlock(String line) { 271 return getCommentToken(line, false); 272 } 273 274 boolean isCommentLine(String line) { 275 return getCommentToken(line, true) != null; 276 } 277 278}