Package com.tek42.perforce.parse

Source Code of com.tek42.perforce.parse.AbstractPerforceTemplate$ResponseFilter

/*
*  P4Java - java integration with Perforce SCM
*  Copyright (C) 2007-,  Mike Wille, Tek42
*
*  This library is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public
*  License as published by the Free Software Foundation; either
*  version 2.1 of the License, or (at your option) any later version.
*
*  This library is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*  Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*
*  You can contact the author at:
*
*  Web:  http://tek42.com
*  Email:  mike@tek42.com
*  Mail:  755 W Big Beaver Road
*      Suite 1110
*      Troy, MI 48084
*/

package com.tek42.perforce.parse;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FilterWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.slf4j.Logger;

import com.tek42.perforce.Depot;
import com.tek42.perforce.PerforceException;
import com.tek42.perforce.process.Executor;
import hudson.plugins.perforce.PerforceSCM;
import hudson.plugins.perforce.utils.TimedStreamCloser;
import java.io.InputStream;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import org.slf4j.LoggerFactory;
import org.apache.commons.lang.time.StopWatch;

/**
* Provides default functionality for interacting with Perforce using the template design pattern.
*
* @author Mike Wille
*/
public abstract class AbstractPerforceTemplate {
    private static final String p4errors[] = new String[] {
            "Connect to server failed; check $P4PORT",
            "Perforce password (P4PASSWD) invalid or unset.",
            "Password not allowed at this server security level, use 'p4 login'",
            "Can't create a new user - over license quota.",
            "Client '*' can only be used from host '*'",
            "Access for user '",
            "Your session has expired, please login again.",
            "You don't have permission for this operation.",
            "Password invalid.",
            "The authenticity of",
        };
   
    private static final int P4_EXECUTOR_CHECK_PERIOD = 2000;

    @SuppressWarnings("unused")
    private transient Logger logger;   // Obsolete field, present just to keep demarshaller happy
    @SuppressWarnings("unused")
    private transient String errors[];   // Obsolete field, present just to keep demarshaller happy

    private final Depot depot;
    final transient String maxError = "Request too large";

    public AbstractPerforceTemplate(Depot depot) {
            this.depot = depot;
    }

    public Logger getLogger()
    {
        if(depot.getLogger() != null){
            return depot.getLogger();
        } else {
            return LoggerFactory.getLogger(this.getClass());
        }
    }

    /**
   * Parses lines of formatted text for a list of values. Tokenizes each line into columns and adds the column
   * specified by index to the list.
   *
   * @param response  The response from perforce to parse
   * @param index    The column index to add to the list
   * @return  A List of strings parsed from the response
   */
  protected List<String> parseList(StringBuilder response, int index) {
    StringTokenizer lines = new StringTokenizer(response.toString(), "\n\r");
    List<String> list = new ArrayList<String>(100);
    while(lines.hasMoreElements()) {
      StringTokenizer columns = new StringTokenizer(lines.nextToken());
      for(int column = 0; column < index; column++) {
        columns.nextToken();
      }
      list.add(columns.nextToken());
    }
    return list;

  }

  /**
   * Check to see if the perforce request resulted in a "too many results" error.  If so, special handling needs
   * to happen.
   *
   * @param response The response from perforce
   * @return  True if the limit was reached, false otherwise.
   */
  protected boolean hitMax(StringBuilder response) {
    return response.toString().startsWith(maxError);
  }

        /**
         * Used to filter the response from perforce so the API can throw out
         * useless lines and thus save memory during large operations.
         * ie. synced/refreshed lines from 'p4 sync'
         */
        public abstract static class ResponseFilter {
            public abstract boolean accept(String line);
            public boolean reject(String line){
                return !accept(line);
            }
        }

  /**
   * Adds any extra parameters that need to be applied to all perforce commands. For example, adding the login ticket
   * to authenticate with.
   *
   * @param cmd
   *            String array that will be executed
   * @return A (possibly) modified string array to be executed in place of the original.
   */
  protected String[] getExtraParams(String cmd[]) {
    String ticket = depot.getP4Ticket();

    if(ticket != null) {
      // Insert the ticket for the password if tickets are being used...
      String newCmds[] = new String[cmd.length + 2];
      newCmds[0] = getP4Exe();
      newCmds[1] = "-P";
      newCmds[2] = ticket;
      for(int i = 3; (i - 2) < cmd.length; i++) {
        newCmds[i] = cmd[i - 2];
      }
      cmd = newCmds;
    } else {
        cmd[0] = getP4Exe();
    }
    return cmd;
  }

  /**
   * Handles the IO for opening a process, writing to it, flushing, closing, and then handling any errors.
   *
   * @param object  The perforce object to save
   * @param builder  The builder responsible for saving the object
   * @throws PerforceException  If there is any errors thrown from perforce
   */
  @SuppressWarnings("unchecked")
  protected void saveToPerforce(Object object, Builder builder) throws PerforceException {
    boolean loop = false;
    boolean attemptLogin = true;

    //StringBuilder response = new StringBuilder();
    do {
      int mesgIndex = -1;//, count = 0;
      Executor p4 = depot.getExecFactory().newExecutor();
      String debugCmd = "";
      try {
        String cmds[] = getExtraParams(builder.getSaveCmd(getP4Exe(), object));

        // for exception reporting...
        for(String cm : cmds) {
          debugCmd += cm + " ";
        }

        // back to our regularly scheduled programming...
        p4.exec(cmds);
        BufferedReader reader = p4.getReader();

        // Maintain a log of what was sent to p4 on std input
        final StringBuilder log = new StringBuilder();

        // Conditional use of std input for saving the perforce entity
        if(builder.requiresStandardInput()) {
          BufferedWriter writer = p4.getWriter();
          Writer fwriter = new FilterWriter(writer) {
            public void write(String str) throws IOException {
              log.append(str);
              out.write(str);
            }
          };
          builder.save(object, fwriter);
          fwriter.flush();
          fwriter.close();
        }

        String line;
                StringBuilder error = new StringBuilder();
                StringBuilder info = new StringBuilder();
        int exitCode = 0;

        while((line = reader.readLine()) != null) {

          // Check for authentication errors...
            if (mesgIndex == -1)
                mesgIndex = checkAuthnErrors(line);

            if (mesgIndex != -1) {
                error.append(line);

            } else if(line.startsWith("error")) {
            if(!line.trim().equals("") && (line.indexOf("up-to-date") < 0) && (line.indexOf("no file(s) to resolve") < 0)) {
              error.append(line.substring(6));
            }

          } else if(line.startsWith("exit")) {
            exitCode = Integer.parseInt(line.substring(line.indexOf(" ") + 1, line.length()));

          } else {
            if(line.indexOf(":") > -1)
              info.append(line.substring(line.indexOf(":")));
            else
              info.append(line);
          }
        }
        reader.close();

        loop = false;
        // If we failed to execute because of an authentication issue, try a p4 login.
        if(mesgIndex == 1 || mesgIndex == 2 || mesgIndex == 6 || mesgIndex == 9) {
            if (attemptLogin) {
                      // password is unset means that perforce isn't using the environment var P4PASSWD
                      // Instead it is using tickets. We must attempt to login via p4 login, then
                      // retry this cmd.
                      p4.close();
                            trustIfSSL();
                      login();
                      loop = true;
                      attemptLogin = false;
                      mesgIndex = -1; // cancel this error for now
                      continue;
            }
        }
       
        if(mesgIndex != -1 || exitCode != 0) {
          if(error.length() != 0) {
              error.append("\nFor Command: ").append(debugCmd);
              if (log.length() > 0) {
                  error.append("\nWith Data:\n===================\n");
                  error.append(log);
                            error.append("\n===================\n");
              }
                        throw new PerforceException(error.toString());
          }
          throw new PerforceException(info.toString());
        }

      } catch(IOException e) {
        throw new PerforceException("Failed to open connection to perforce", e);
      } finally {
                                try{
                                    p4.getWriter().close();
                                } catch (IOException e) {
                                    //failed to close pipe, but we can't do much about that
                                }
                                try{
                                    p4.getReader().close();
                                } catch (IOException e) {
                                    //failed to close pipe, but we can't do much about that
                                }
        p4.close();
      }
    } while(loop);
  }

    /**
   * Executes a perforce command and returns the output as a StringBuilder.
   *
   * @param cmd  The perforce commands to execute.  Each command and argument is it's own array element
   * @return  The response from perforce as a stringbuilder
   * @throws PerforceException  If perforce throws any errors
   */
        protected StringBuilder getPerforceResponse(String cmd[]) throws PerforceException {
            return getPerforceResponse(cmd, new ResponseFilter(){
                @Override
                public boolean accept(String line) {
                    return true;
                }
            });
        }

  protected StringBuilder getPerforceResponse(String origcmd[], ResponseFilter filter) throws PerforceException {
    // TODO: Create a way to wildcard portions of the error checking.  Add method to check for these errors.
    boolean loop = false;
    boolean attemptLogin = true;

    List<String> lines = null;
    int totalLength = 0;
               
    do {
      int mesgIndex = -1, count = 0;
      Executor p4 = depot.getExecFactory().newExecutor();
                       
      String debugCmd = "";
      // get entire cmd to execute
                        String cmd[] = getExtraParams(origcmd);
                       
      // setup information for logging...
      for(String cm : cmd) {
        debugCmd += cm + " ";
      }

      // Perform execution and IO
      p4.exec(cmd);
      BufferedReader reader = p4.getReader();
      String line = null;
      totalLength = 0;
      lines = new ArrayList<String>(1024);
                        TimedStreamCloser timedStreamCloser=null;
      try
      {
                             PerforceSCM.PerforceSCMDescriptor scmDescr = PerforceSCM.getInstance();
                             p4.getWriter().close();
                             int timeout = -1;
                             if(scmDescr.hasP4ReadlineTimeout()) { // Implementation with timeout
                               timeout = scmDescr.getP4ReadLineTimeout();
                            
                             timedStreamCloser = new TimedStreamCloser(p4.getInputStream(), timeout);
                             timedStreamCloser.start();

                             while((line = reader.readLine()) != null) {
                                timedStreamCloser.reset();
                                // only check for errors if we have not found one already
                                if (mesgIndex == -1)
                                    mesgIndex = checkAuthnErrors(line);
                                if(filter.reject(line)) continue;
                                lines.add(line);
                                totalLength += line.length();
                                count++;
                            }
                            if(timedStreamCloser.timedOut()) {
                                throw new PerforceException("Perforce operation timed out after " + timeout + " seconds.");
                            }
      }
      catch(IOException ioe)
      {
        //this is generally not anything to worry about.  The underlying
        //perforce process terminated and that causes java to be angry
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw, true);
        ioe.printStackTrace(pw);
        pw.flush();
        sw.flush();
        getLogger().warn("Perforce process terminated suddenly");
        getLogger().warn(sw.toString());
      }
      finally{
                            if(timedStreamCloser!=null) timedStreamCloser.interrupt();
                            try{
                                p4.getWriter().close();
                            } catch (IOException e) {
                                getLogger().warn("Write pipe failed to close.");
                            }
                            try{
                                p4.getReader().close();
                            } catch (IOException e) {
                                getLogger().warn("Read pipe failed to close.");
                            }
                            p4.close();
      }
      loop = false;
      // If we failed to execute because of an authentication issue, try a p4 login.
      if(attemptLogin && (mesgIndex == 1 || mesgIndex == 2 || mesgIndex == 6 || mesgIndex == 9)) {
        // password is unset means that perforce isn't using the environment var P4PASSWD
        // Instead it is using tickets. We must attempt to login via p4 login, then
        // retry this cmd.
        p4.close();
                                trustIfSSL();
        login();
        loop = true;
        attemptLogin = false;
        continue;
      }

      // We aren't using the exact message because we want to add the username for more info
      if(mesgIndex == 4)
        throw new PerforceException("Access for user '" + depot.getUser() + "' has not been enabled by 'p4 protect'");
      if(mesgIndex != -1)
        throw new PerforceException(p4errors[mesgIndex]);
      if(count == 0)
        throw new PerforceException("No output for: " + debugCmd);
    } while(loop);

    StringBuilder response = new StringBuilder(totalLength + lines.size());
    for (String line : lines)
        {
          response.append(line);
          response.append("\n");
        }

    return response;
  }

    /**
     * Executes a p4 command and returns the output as list of lines.
     *
     * TODO Introduce a method that handles prefixed messages (i.e. "p4 -s <sub-command>"),
     * and can thus stop reading once if reads the "exit: <exit-code>" line, which
     * should avoid the "expected" Exception at EOF.
     *
     * @param cmd
     *      The perforce command to execute.  The command and arguments are
     *      each in their own array element (e.g. cmd = {"p4", "info"}).
     * @return
     *      The response from perforce as a list
     * @throws PerforceException
     */
    protected List<String> getRawPerforceResponseLines(String cmd[]) throws PerforceException {
        List<String> lines = new ArrayList<String>(1024);

        Executor p4 = depot.getExecFactory().newExecutor();
        String debugCmd = "";
        // get entire cmd to execute
        cmd = getExtraParams(cmd);

        // setup information for logging...
        for(String cm : cmd) {
            debugCmd += cm + " ";
        }

        // Perform execution and IO
        p4.exec(cmd);

        try
        {
            BufferedReader reader = p4.getReader();
            p4.getWriter().close();
            String line = null;
            while((line = reader.readLine()) != null) {
                lines.add(line);
            }
        }
        catch(IOException ioe)
        {
            //this is generally not anything to worry about.  The underlying
            //perforce process terminated and that causes java to be angry.

            // TODO Given the above comment, should we bother to log a warning?
            // See this blog for a discussion of IOException with message "Write end dead" from pipes:
            //      http://techtavern.wordpress.com/2008/07/16/whats-this-ioexception-write-end-dead/

            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw, true);
            ioe.printStackTrace(pw);
            pw.flush();
            sw.flush();
            getLogger().warn("IOException reading from Perforce process (may just be EOF)");
            getLogger().warn(sw.toString());
        }
        finally{
            try{
                p4.getWriter().close();
            } catch (IOException e) {
                getLogger().warn("Write pipe failed to close.");
            }
            try{
                p4.getReader().close();
            } catch (IOException e) {
                getLogger().warn("Read pipe failed to close.");
            }
            p4.close();
        }

        return lines;
    }

    /**
     * Used by calls that make use of p4.exe's python dictionary output format.
     * @param cmd
     * @return
     * @throws PerforceException
     */

    protected byte[] getRawPerforceResponseBytes(String cmd[]) throws PerforceException {
        List<Byte> bytes = new ArrayList<Byte>(1024);

        Executor p4 = depot.getExecFactory().newExecutor();
        String debugCmd = "";
        // get entire cmd to execute
        cmd = getExtraParams(cmd);

        // setup information for logging...
        for(String cm : cmd) {
            debugCmd += cm + " ";
        }

        // Perform execution and IO
        p4.exec(cmd);

        try
        {
            byte[] cbuf = new byte[1024];
            InputStream input = p4.getInputStream();
            p4.getWriter().close();
            int readCount = -1;
            while((readCount = input.read(cbuf, 0, 1024)) != -1) {
                for(int i=0; i<readCount; i++){
                    bytes.add(new Byte((byte)(cbuf[i]&0xff)));
                }
            }
        }
        catch(IOException ioe)
        {
            //this is generally not anything to worry about.  The underlying
            //perforce process terminated and that causes java to be angry.

            // TODO Given the above comment, should we bother to log a warning?
            // See this blog for a discussion of IOException with message "Write end dead" from pipes:
            //      http://techtavern.wordpress.com/2008/07/16/whats-this-ioexception-write-end-dead/

            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw, true);
            ioe.printStackTrace(pw);
            pw.flush();
            sw.flush();
            getLogger().warn("IOException reading from Perforce process (may just be EOF)");
            getLogger().warn(sw.toString());
        }
        finally{
            try{
                p4.getWriter().close();
            } catch (IOException e) {
                getLogger().warn("Write pipe failed to close.");
            }
            try{
                p4.getReader().close();
            } catch (IOException e) {
                getLogger().warn("Read pipe failed to close.");
            }
            p4.close();
        }
        byte[] byteArray = new byte[bytes.size()];
        for(int i=0; i<bytes.size(); i++){
            byteArray[i] = bytes.get(i).byteValue();
        }
        return byteArray;
    }

        /**
   * Tries to perform a p4 login if the security level on the server is set to level 3 and no ticket was set via
   * depot.setP4Ticket().
   * <p>
   * Unfortunately, this likely doesn't work on windows.
   *
   * @throws PerforceException  If perforce throws any errors
   */
  protected void login() throws PerforceException {

    try {
        // try the default location for p4 executable
            String ticket = null;
            try {
                ticket = p4Login(getP4Exe());
            } catch (PerforceException e) {
                // Strange error under hudson's execution of unit tests.  It appears
                // that the environment is not setup correctly from within hudson.  The sh shell
                // cannot find the p4 executable.  So we'll try again with a hard coded path.
                // Though, I don't believe this problem exists outside of the build environment,
                // and wouldn't normally worry, I still want to be able to test security level 3
                // from the automated build...
                getLogger().warn("Login with '" + getP4Exe() + "' failed: " + e.getMessage());
                try {
                    ticket = p4Login("/usr/bin/p4");
                } catch (PerforceException e1) {
                    // throw the original exception and not the one caused by the workaround
                    getLogger().warn("Attempt to workaround p4 executable location failed", e1);
                    throw e;
                }
            }

          // if we obtained a ticket, save it for later use. Our environment setup by Depot can't usually
          // see the .p4tickets file.
          if (ticket != null && !ticket.contains("Enter password:")) {
              getLogger().warn("Using p4 issued ticket.");
              depot.setP4Ticket(ticket);
          }

    } catch(IOException e) {
      throw new PerforceException("Unable to login via p4 login due to IOException: " + e.getMessage());
    }
  }

    /**
     * Read the last line of output which should be the ticket.
     *
     * @param p4Exe the perforce executable with or without full path information
     * @return the p4 ticket
     * @throws IOException if an I/O error prevents this from working
     * @throws PerforceException if the execution of the p4Exe fails
     */
    private String p4Login(String p4Exe) throws IOException, PerforceException {
        Executor login = depot.getExecFactory().newExecutor();
        login.exec(new String[] { p4Exe, "login", "-a", "-p" });

        try {
            // "echo" the password for the p4 process to read
            BufferedWriter writer = login.getWriter();
            try {
                writer.write(depot.getPassword() + "\n");
            } finally {
                // help the writer move the data
                writer.flush();
            }
            // read the ticket from the output
            String ticket = null;
            BufferedReader reader = login.getReader();
            String line;
            // The line matching ^[0-9A-F]{32}$ will be the ticket
            while ((line = reader.readLine()) != null) {
                int error = checkAuthnErrors(line);
                if (error != -1)
                    throw new PerforceException("Login attempt failed: " + line);
                if (line.trim().matches("^[0-9A-F]{32}$"))
                    ticket = line;
            }
           
            return ticket;
        } finally {
            login.close();
        }
    }
   
    /**
     * Trust the perforce server if using SSL
     */
    private void trustIfSSL() throws PerforceException {
        Executor trust = depot.getExecFactory().newExecutor();
        String p4Port = depot.getPort();
        if(p4Port.toLowerCase().startsWith("ssl:")){
            trust.exec(new String[] { getP4Exe(), "-p", depot.getPort(), "trust", "-y" });
            try{
                trust.getWriter().close();
                BufferedReader reader = trust.getReader();
                String line;
                // The line matching ^[0-9A-F]{32}$ will be the ticket
                while ((line = reader.readLine()) != null) {
                    int error = checkAuthnErrors(line);
                    if (error != -1)
                        throw new PerforceException("Trust attempt failed: " + line);
                }
            } catch (IOException e) {
                throw new PerforceException("Could not establish ssl trust with perforce server", e);
            }
            trust.close();
        }
    }
   
    /**
     * Check for authentication errors.
     *
     * @param line the perforce response line
     * @return the index in the p4errors array or -1
     */
    private int checkAuthnErrors(String line) {
        for (int i = 0; i < p4errors.length; i++) {
          if (line.indexOf(p4errors[i]) != -1)
            return i;
   
        }
        return -1;
    }

    protected String getP4Exe() {
        return depot.getExecutable();
    }
}
TOP

Related Classes of com.tek42.perforce.parse.AbstractPerforceTemplate$ResponseFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.