/*
* The MIT License
*
* Copyright 2013 Mirko Friedenhagen.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.plugins.jobConfigHistory;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.servlet.ServletException;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import difflib.DiffUtils;
import difflib.Patch;
import difflib.StringUtills;
import hudson.model.Action;
import hudson.plugins.jobConfigHistory.SideBySideView.Line;
import hudson.security.AccessControlled;
import hudson.util.MultipartFormDataParser;
import jenkins.model.Jenkins;
/**
* Implements some basic methods needed by the
* {@link JobConfigHistoryRootAction} and {@link JobConfigHistoryProjectAction}.
*
* @author Mirko Friedenhagen
*/
public abstract class JobConfigHistoryBaseAction implements Action {
/**
* The jenkins instance.
*/
private final Jenkins jenkins;
/**
* Set the {@link Jenkins} instance.
*/
public JobConfigHistoryBaseAction() {
jenkins = Jenkins.getInstance();
}
/**
* For tests only.
*
* @param jenkins
* injected jenkins
*/
JobConfigHistoryBaseAction(Jenkins jenkins) {
this.jenkins = jenkins;
}
@Override
public String getDisplayName() {
return Messages.displayName();
}
@Override
public String getUrlName() {
return JobConfigHistoryConsts.URLNAME;
}
/**
* Returns how the config file should be formatted in configOutput.jelly: as
* plain text or xml.
*
* @return "plain" or "xml"
*/
public final String getOutputType() {
if (("xml").equalsIgnoreCase(getRequestParameter("type"))) {
return "xml";
}
return "plain";
}
/**
* Checks the url parameter 'timestamp' and returns true if it is parseable
* as a date.
*
* @param timestamp
* Timestamp of config change.
* @return True if timestamp is okay.
*/
protected boolean checkTimestamp(String timestamp) {
if (timestamp == null || "null".equals(timestamp)) {
return false;
}
PluginUtils.parsedDate(timestamp);
return true;
}
/**
* Returns the parameter named {@code parameterName} from current request.
*
* @param parameterName
* name of the parameter.
* @return value of the request parameter or null if it does not exist.
*/
protected String getRequestParameter(final String parameterName) {
return getCurrentRequest().getParameter(parameterName);
}
/**
* See whether the current user may read configurations in the object
* returned by
* {@link JobConfigHistoryBaseAction#getAccessControlledObject()}.
*/
protected abstract void checkConfigurePermission();
/**
* Returns whether the current user may read configurations in the object
* returned by
* {@link JobConfigHistoryBaseAction#getAccessControlledObject()}.
*
* @return true if the current user may read configurations.
*/
protected abstract boolean hasConfigurePermission();
/**
* Returns the jenkins instance.
*
* @return the jenkins
*/
protected Jenkins getJenkins() {
return jenkins;
}
/**
* Returns the object for which we want to provide access control.
*
* @return the access controlled object.
*/
protected abstract AccessControlled getAccessControlledObject();
/**
* Returns side-by-side (i.e. human-readable) diff view lines.
*
* @param diffLines
* Unified diff as list of Strings.
* @return Nice and clean diff as list of single Lines.
* @throws IOException
* if reading one of the config files does not succeed.
*/
public final List<Line> getDiffLines(List<String> diffLines)
throws IOException {
return new GetDiffLines(diffLines).get();
}
/**
* Returns a unified diff between two string arrays.
*
* @param file1
* first config file.
* @param file2
* second config file.
* @param file1Lines
* the lines of the first file.
* @param file2Lines
* the lines of the second file.
* @return unified diff
*/
protected final String getDiffAsString(final File file1, final File file2,
final String[] file1Lines, final String[] file2Lines) {
final Patch patch = DiffUtils.diff(Arrays.asList(file1Lines),
Arrays.asList(file2Lines));
final List<String> unifiedDiff = DiffUtils.generateUnifiedDiff(
file1.getPath(), file2.getPath(), Arrays.asList(file1Lines),
patch, 3);
return StringUtills.join(unifiedDiff, "\n") + "\n";
}
/**
* Parses the incoming {@literal POST} request and redirects as
* {@literal GET showDiffFiles}.
*
* @param req
* incoming request
* @param rsp
* outgoing response
* @throws ServletException
* when parsing the request as {@link MultipartFormDataParser}
* does not succeed.
* @throws IOException
* when the redirection does not succeed.
*/
public void doDiffFiles(StaplerRequest req, StaplerResponse rsp)
throws ServletException, IOException {
String timestamp1 = req.getParameter("timestamp1");
String timestamp2 = req.getParameter("timestamp2");
if (PluginUtils.parsedDate(timestamp1)
.after(PluginUtils.parsedDate(timestamp2))) {
timestamp1 = req.getParameter("timestamp2");
timestamp2 = req.getParameter("timestamp1");
}
rsp.sendRedirect("showDiffFiles?timestamp1=" + timestamp1
+ "×tamp2=" + timestamp2);
}
/**
* Action when 'Prev' or 'Next' button in showDiffFiles.jelly is pressed.
* Forwards to the previous or next diff.
*
* @param req
* StaplerRequest created by pressing the button
* @param rsp
* Outgoing StaplerResponse
* @throws IOException
* If XML file can't be read
*/
public final void doDiffFilesPrevNext(StaplerRequest req,
StaplerResponse rsp) throws IOException {
final String timestamp1 = req.getParameter("timestamp1");
final String timestamp2 = req.getParameter("timestamp2");
rsp.sendRedirect("showDiffFiles?timestamp1=" + timestamp1
+ "×tamp2=" + timestamp2);
}
/**
* Overridable for tests.
*
* @return current request
*/
protected StaplerRequest getCurrentRequest() {
return Stapler.getCurrentRequest();
}
/**
* Returns the plugin for tests.
*
* @return plugin
*/
protected JobConfigHistory getPlugin() {
return PluginUtils.getPlugin();
}
/**
* For tests.
*
* @return historyDao
*/
protected HistoryDao getHistoryDao() {
return PluginUtils.getHistoryDao();
}
}