283N/A/*
283N/A * CDDL HEADER START
283N/A *
283N/A * The contents of this file are subject to the terms of the
283N/A * Common Development and Distribution License (the "License").
283N/A * You may not use this file except in compliance with the License.
283N/A *
283N/A * See LICENSE.txt included in this distribution for the specific
283N/A * language governing permissions and limitations under the License.
283N/A *
283N/A * When distributing Covered Code, include this CDDL HEADER in each
283N/A * file and include the License file at LICENSE.txt.
283N/A * If applicable, add the following below this CDDL HEADER, with the
283N/A * fields enclosed by brackets "[]" replaced with your own identifying
283N/A * information: Portions Copyright [yyyy] [name of copyright owner]
283N/A *
283N/A * CDDL HEADER END
283N/A */
407N/A
1248N/A/*
1248N/A * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
1248N/A */
283N/Apackage org.opensolaris.opengrok.history;
283N/A
283N/Aimport java.io.BufferedInputStream;
283N/Aimport java.io.BufferedReader;
283N/Aimport java.io.File;
283N/Aimport java.io.FileInputStream;
283N/Aimport java.io.IOException;
283N/Aimport java.io.InputStream;
283N/Aimport java.io.InputStreamReader;
283N/Aimport java.util.ArrayList;
1018N/Aimport java.util.Arrays;
633N/Aimport java.util.List;
416N/Aimport java.util.logging.Level;
1327N/Aimport java.util.logging.Logger;
1018N/Aimport java.util.regex.Pattern;
1327N/A
1462N/Aimport org.opensolaris.opengrok.configuration.Configuration;
633N/Aimport org.opensolaris.opengrok.util.Executor;
1195N/Aimport org.opensolaris.opengrok.util.IOUtils;
283N/A
283N/A/**
283N/A * Access to a ClearCase repository.
283N/A */
290N/Apublic class ClearCaseRepository extends Repository {
815N/A private static final long serialVersionUID = 1L;
1473N/A private static final Logger logger =
1473N/A Logger.getLogger(ClearCaseRepository.class.getName());
1182N/A /** The property name used to obtain the client command for this repository. */
1190N/A public static final String CMD_PROPERTY_KEY =
1462N/A Configuration.PROPERTY_KEY_PREFIX + "history.ClearCase";
1182N/A /** The command to use to access the repository if none was given explicitly */
1182N/A public static final String CMD_FALLBACK = "cleartool";
1018N/A
283N/A private boolean verbose;
283N/A
1462N/A /**
1462N/A * Create a new instance of type {@code ClearCase}.
1462N/A */
664N/A public ClearCaseRepository() {
664N/A type = "ClearCase";
749N/A datePattern = "yyyyMMdd.HHmmss";
664N/A }
664N/A
283N/A /**
283N/A * Use verbose log messages, or just the summary
283N/A * @return true if verbose log messages are used for this repository
283N/A */
283N/A public boolean isVerbose() {
283N/A return verbose;
283N/A }
283N/A
283N/A /**
283N/A * Specify if verbose log messages or just the summary should be used
283N/A * @param verbose set to true if verbose messages should be used
283N/A */
283N/A public void setVerbose(boolean verbose) {
283N/A this.verbose = verbose;
283N/A }
283N/A
633N/A /**
633N/A * Get an executor to be used for retrieving the history log for the
633N/A * named file.
1190N/A *
1473N/A * @param file The file to retrieve history for (canonical path incl. source
1473N/A * root).
633N/A * @return An Executor ready to be started
633N/A */
1016N/A Executor getHistoryLogExecutor(final File file) throws IOException {
1016N/A String abs = file.getCanonicalPath();
283N/A String filename = "";
283N/A if (abs.length() > directoryName.length()) {
283N/A filename = abs.substring(directoryName.length() + 1);
283N/A }
1190N/A
633N/A List<String> cmd = new ArrayList<String>();
1182N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1182N/A cmd.add(this.cmd);
633N/A cmd.add("lshistory");
283N/A if (file.isDirectory()) {
633N/A cmd.add("-dir");
283N/A }
633N/A cmd.add("-fmt");
633N/A cmd.add("%e\n%Nd\n%Fu (%u)\n%Vn\n%Nc\n.\n");
633N/A cmd.add(filename);
283N/A
633N/A return new Executor(cmd, new File(getDirectoryName()));
1190N/A }
283N/A
1462N/A /**
1462N/A * {@inheritDoc}
1462N/A */
1462N/A @SuppressWarnings("resource")
815N/A @Override
1183N/A public InputStream getHistoryGet(String parent, String basename, String rev)
1183N/A {
563N/A InputStream ret = null;
563N/A
283N/A File directory = new File(directoryName);
283N/A
283N/A Process process = null;
283N/A try {
1183N/A String filename = (new File(parent, basename)).getCanonicalPath()
1183N/A .substring(directoryName.length() + 1);
283N/A final File tmp = File.createTempFile("opengrok", "tmp");
1016N/A String tmpName = tmp.getCanonicalPath();
283N/A
283N/A // cleartool can't get to a previously existing file
460N/A if (tmp.exists() && !tmp.delete()) {
1327N/A logger.warning("Failed to remove temporary file used by history cache");
357N/A }
283N/A
283N/A String decorated = filename + "@@" + rev;
1182N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1182N/A String argv[] = {cmd, "get", "-to", tmpName, decorated};
283N/A process = Runtime.getRuntime().exec(argv, null, directory);
283N/A
404N/A drainStream(process.getInputStream());
283N/A
433N/A if(waitFor(process) != 0) {
433N/A return null;
433N/A }
283N/A
566N/A ret = new BufferedInputStream(new FileInputStream(tmp)) {
403N/A
815N/A @Override
403N/A public void close() throws IOException {
283N/A super.close();
283N/A // delete the temporary file on close
403N/A if (!tmp.delete()) {
283N/A // failed, lets do the next best thing then ..
283N/A // delete it on JVM exit
283N/A tmp.deleteOnExit();
283N/A }
283N/A }
566N/A };
563N/A } catch (Exception exp) {
1327N/A logger.warning("Failed to get history for '" + parent
1327N/A + File.separator + basename + "': " + exp.getMessage());
1327N/A logger.log(Level.FINE, "getHistoryGet", exp);
283N/A } finally {
283N/A // Clean up zombie-processes...
283N/A if (process != null) {
283N/A try {
283N/A process.exitValue();
283N/A } catch (IllegalThreadStateException exp) {
283N/A // the process is still running??? just kill it..
283N/A process.destroy();
283N/A }
283N/A }
283N/A }
563N/A
563N/A return ret;
283N/A }
283N/A
404N/A /**
404N/A * Drain all data from a stream and close it.
404N/A * @param in the stream to drain
404N/A * @throws IOException if an I/O error occurs
404N/A */
404N/A private static void drainStream(InputStream in) throws IOException {
404N/A while (true) {
662N/A long skipped = 0;
662N/A try {
662N/A skipped = in.skip(32768L);
662N/A } catch (IOException ioe) {
1190N/A // ignored - stream isn't seekable, but skipped variable still
1183N/A // has correct value.
1327N/A logger.log(Level.FINEST, "Stream not seekable", ioe);
662N/A }
460N/A if (skipped == 0 && in.read() == -1) {
460N/A // No bytes skipped, checked that we've reached EOF with read()
460N/A break;
404N/A }
404N/A }
1195N/A IOUtils.close(in);
404N/A }
404N/A
283N/A /**
1473N/A * {@inheritDoc}
283N/A */
815N/A @Override
1473N/A public Annotation annotate(File file, String revision) throws IOException {
283N/A ArrayList<String> argv = new ArrayList<String>();
283N/A
1182N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1182N/A argv.add(cmd);
283N/A argv.add("annotate");
283N/A argv.add("-nheader");
283N/A argv.add("-out");
283N/A argv.add("-");
283N/A argv.add("-f");
283N/A argv.add("-fmt");
283N/A argv.add("%u|%Vn|");
283N/A
283N/A if (revision != null) {
283N/A argv.add(revision);
283N/A }
283N/A argv.add(file.getName());
283N/A ProcessBuilder pb = new ProcessBuilder(argv);
283N/A pb.directory(file.getParentFile());
357N/A Process process = null;
357N/A BufferedReader in = null;
283N/A try {
357N/A process = pb.start();
403N/A in = new BufferedReader(new InputStreamReader(process.getInputStream()));
347N/A Annotation a = new Annotation(file.getName());
347N/A String line;
347N/A while ((line = in.readLine()) != null) {
347N/A String parts[] = line.split("\\|");
347N/A String aAuthor = parts[0];
347N/A String aRevision = parts[1];
347N/A aRevision = aRevision.replace('\\', '/');
283N/A
1384N/A a.addLine(aRevision, aAuthor);
283N/A }
283N/A return a;
283N/A } finally {
1195N/A IOUtils.close(in);
357N/A if (process != null) {
357N/A try {
357N/A process.exitValue();
357N/A } catch (IllegalThreadStateException e) {
357N/A process.destroy();
357N/A }
283N/A }
283N/A }
283N/A }
283N/A
1462N/A /**
1462N/A * {@inheritDoc}
1462N/A */
815N/A @Override
298N/A public boolean fileHasAnnotation(File file) {
283N/A return true;
283N/A }
283N/A
1462N/A private static int waitFor(Process process) {
283N/A do {
283N/A try {
403N/A return process.waitFor();
283N/A } catch (InterruptedException exp) {
1327N/A // ok
283N/A }
283N/A } while (true);
283N/A }
283N/A
1462N/A /**
1462N/A * {@inheritDoc}
1462N/A */
436N/A @SuppressWarnings("PMD.EmptyWhileStmt")
815N/A @Override
459N/A public void update() throws IOException {
283N/A Process process = null;
357N/A BufferedReader in = null;
283N/A try {
283N/A File directory = new File(getDirectoryName());
283N/A
283N/A // Check if this is a snapshot view
1182N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1473N/A String[] argv = { cmd, "catcs" };
283N/A process = Runtime.getRuntime().exec(argv, null, directory);
403N/A in = new BufferedReader(new InputStreamReader(process.getInputStream()));
347N/A boolean snapshot = false;
347N/A String line;
347N/A while (!snapshot && (line = in.readLine()) != null) {
347N/A snapshot = line.startsWith("load");
283N/A }
283N/A if (waitFor(process) != 0) {
403N/A return;
283N/A }
1195N/A IOUtils.close(in);
357N/A in = null; // To avoid double close in finally clause
403N/A if (snapshot) {
283N/A // It is a snapshot view, we need to update it manually
1182N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1182N/A argv = new String[]{cmd, "update", "-overwrite", "-f"};
283N/A process = Runtime.getRuntime().exec(argv, null, directory);
403N/A in = new BufferedReader(new InputStreamReader(process.getInputStream()));
283N/A // consume output
403N/A while ((line = in.readLine()) != null) {
421N/A // do nothing
403N/A }
283N/A
283N/A if (waitFor(process) != 0) {
403N/A return;
283N/A }
283N/A }
283N/A } finally {
1195N/A IOUtils.close(in);
1195N/A
357N/A if (process != null) {
357N/A try {
357N/A process.exitValue();
357N/A } catch (IllegalThreadStateException e) {
357N/A process.destroy();
357N/A }
283N/A }
283N/A }
283N/A }
283N/A
1462N/A /**
1462N/A * {@inheritDoc}
1462N/A */
815N/A @Override
283N/A public boolean fileHasHistory(File file) {
283N/A // Todo: is there a cheap test for whether ClearCase has history
283N/A // available for a file?
283N/A // Otherwise, this is harmless, since ClearCase's commands will just
283N/A // print nothing if there is no history.
283N/A return true;
283N/A }
314N/A
1462N/A /**
1462N/A * {@inheritDoc}
1462N/A */
314N/A @Override
1018N/A public boolean isWorking() {
1182N/A if (working == null) {
1182N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1248N/A working = checkCmd(cmd, "–version");
1182N/A }
1182N/A return working.booleanValue();
1018N/A }
1018N/A
1473N/A /**
1473N/A * {@inheritDoc}
1473N/A */
1018N/A @Override
1473N/A public boolean isRepositoryFor(File file) {
314N/A // if the parent contains a file named "view.dat" or
1018N/A // the parent is named "vobs" or the canonical path
1018N/A // is found in "cleartool lsvob -s"
314N/A File f = new File(file, "view.dat");
660N/A if (f.exists()) {
314N/A return true;
1018N/A } else if (file.isDirectory() && file.getName().equalsIgnoreCase("vobs")) {
1018N/A return true;
1018N/A } else if (isWorking()) {
1018N/A try {
1018N/A String canonicalPath = file.getCanonicalPath();
1018N/A for (String vob : getAllVobs()) {
1018N/A if (canonicalPath.equalsIgnoreCase(vob)) {
1018N/A return true;
1018N/A }
1018N/A }
1018N/A } catch (IOException e) {
1327N/A logger.warning("Could not get canonical path for '" + file
1327N/A + "': " + e.getMessage());
1327N/A logger.log(Level.FINE, "isRepositoryFor", e);
1018N/A }
1018N/A }
1018N/A return false;
1018N/A }
1018N/A
1018N/A private static class VobsHolder {
1462N/A @SuppressWarnings("synthetic-access")
1018N/A public static String[] vobs = runLsvob();
1018N/A }
1018N/A
1018N/A private static String[] getAllVobs() {
1018N/A return VobsHolder.vobs;
1018N/A }
1018N/A
1473N/A private static final ClearCaseRepository testRepo = new ClearCaseRepository();
1182N/A
1018N/A private static String[] runLsvob() {
1182N/A if (testRepo.isWorking()) {
1182N/A Executor exec = new Executor(new String[] {testRepo.cmd, "lsvob", "-s"});
1182N/A int rc;
1182N/A if ((rc = exec.exec(true)) == 0) {
1182N/A String output = exec.getOutputString();
1190N/A
1182N/A if (output == null) {
1327N/A logger.warning("[cleartool lsvob -s] output was null");
1182N/A return new String[0];
1182N/A }
1018N/A String sep = System.getProperty("line.separator");
1018N/A String[] vobs = output.split(Pattern.quote(sep));
1327N/A logger.log(Level.FINE, "Found VOBs: {0}", Arrays.asList(vobs));
1018N/A return vobs;
1018N/A }
1327N/A logger.warning("[cleartool lsvob -s] returned non-zero status " + rc);
314N/A }
1182N/A return new String[0];
314N/A }
765N/A
1473N/A /**
1473N/A * {@inheritDoc}
1473N/A */
765N/A @Override
1473N/A public boolean hasHistoryForDirectories() {
765N/A return true;
765N/A }
765N/A
1473N/A /**
1473N/A * {@inheritDoc}
1473N/A */
765N/A @Override
1473N/A public History getHistory(File file) throws HistoryException {
765N/A return new ClearCaseHistoryParser().parse(file, this);
765N/A }
283N/A}