Subversion Repositories XServices

Rev

Rev 170 | View as "text/plain" | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

/*
 *   Copyright 2013 Brian Rosenberger (Brutex Network)
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package net.brutex.emitter;

import net.brutex.emitter.util.EmitterUtil;
import net.brutex.emitter.util.PasswordEncrypter;
import net.brutex.sbm.sbmappservices72.AEWebservicesFaultFault;
import net.brutex.sbm.sbmappservices72.Sbmappservices72PortType;
import net.brutex.sbm.sbmappservices72.api.*;
import net.brutex.svn.SVNAdminCommand;
import net.brutex.svn.SVNAdminExecutor;
import net.brutex.svn.SVNCommitInfo;
import net.brutex.svn.SVNLookExecutor;
import org.apache.axiom.om.*;
import org.apache.axiom.om.xpath.AXIOMXPath;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.*;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.http.client.ClientProtocolException;
import org.apache.log4j.Logger;
import org.jaxen.JaxenException;

import javax.xml.stream.XMLStreamException;
import java.io.*;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * The Class ALFEmitter.
 *
 * @author Brian Rosenberger, bru(at)brutex.de
 * @since 0.1
 */
public class ALFEmitter {

        public static final String VERSION = "0.2";
        private static Logger logger = Logger.getRootLogger();

        //
        // Keys to read from the configuration file.
        //
        private static final String OPTION_SVNLOOK = "svnlook";
        private static final String OPTION_SVNADMIN = "svnadmin";
        private static final String OPTION_LOCALE = "env.LANG";
        private static final String OPTION_ENCODING = "env.encoding";
        private static final String OPTION_ISSUEPATTERN = "issuepattern";
        
        private static final String OPTION_EVENTTEMPLATE = "eventtemplate";
        private static final String OPTION_EVENTNAMESPACE = "eventnamespace";
        private static final String OPTION_EVENTMANAGER_URL = "eventmanager";
        private static final String OPTION_EVENTMANAGER_USER = "eventmanager.user";
        private static final String OPTION_EVENTMANAGER_PASSWORD = "eventmanager.password";
        private static final String OPTION_EVENTMANAGER_ENCRYPTED = "eventmanager.encrypted";
        
        private static final String OPTION_SBM_ENDPOINT = "sbmappservices72";
        private static final String OPTION_SBM_USER = "sbmuser";
        private static final String OPTION_SBM_PASSWORD = "sbmpassword";
        private static final String OPTION_SBM_ENCRYPTED = "sbmencrypted";
        private static final String OPTION_SBM_TABLE = "querytable";
        private static final String OPTION_SBM_QUERY = "query";

        private static final String OPTION_MARKER_LOGMESSAGE = "marker.logmessage";
        private static final String OPTION_MARKER_AUTHOR = "marker.author";
        private static final String OPTION_MARKER_REVISION = "marker.revision";
        private static final String OPTION_MARKER_ADDEDFILES = "marker.addedfiles";
        private static final String OPTION_MARKER_DELETEDFILES = "marker.deletedfiles";
        private static final String OPTION_MARKER_CHANGEDFILES = "marker.changedfiles";
        private static final String OPTION_MARKER_ISSUES = "marker.issues";
        private static final String OPTION_MARKER_INTERNALISSUES = "marker.internalissues";
        private static final String OPTION_REMOVE_ISSUES_FROM_COMMIT = "removeissuesfromcommit";
        
        private static final String OPTION_IS_SOAPENABLED = "isSoapEnabled";
        private static final String OPTION_IS_DROPENABLED = "isDropResponse";
        private static final String OPTION_IS_FORCEFAILENABLED = "forcefail";
        private static final String OPTION_IS_VERIFICATIONENABLED = "isWithVerification";
        private static final String OPTION_IS_UPDATECOMMITMESSAGE = "isWithMessageUpdate";
        private static final String OPTION_IS_WSTRACE = "trace";
        private static final String OPTION_IS_XMLPROCESSINGENABLED = "isXmlProcessingEnabled";

        //
        // Command line parameters
        //
        private static final String PARAM_REPOS = "repos";
        private static final String PARAM_TXN = "txn";
        private static final String PARAM_REV = "rev";
        private static final String PARAM_CONFIG = "conf";
        
        //
        // Member variables
        //
        private final String repos;
        private final String txn;
        private final String rev;
        private SVNCommitInfo info;
        private OMElement template = null;
        private Configuration config;
        private String nss = null;
        private final long startTime;
        
        //Member for SBM endpoint configuration
        private final String endpoint;
        private final String sbm_user;
        private String sbm_pass;
        private final String querytable;
        private final String query;

        
        private final List<String> internalissues = new ArrayList<String>();
        //SBM IssueTypePrefix+IssueId, Title
        private final Map<String, String> issues_titles = new HashMap<String, String>();
        
        /**
         * The main method.
         *
         * @param args the arguments
         */
        public static void main(String[] args) {
                long startTime = System.currentTimeMillis();
                CommandLineParser parser = new BasicParser();
                CommandLine cmd = null;
                try {
                        cmd = parser.parse( getOptions(), args);
                } catch (ParseException e1) {
                        logger.error(e1.getMessage());
                        printHelp();
                        System.exit(1);
                }
                try {
                        ALFEmitter emitter = new ALFEmitter(cmd, startTime);
                } catch (ConfigurationException e) {
                        System.exit(1);
                } catch (MalformedURLException e) {
                        logger.error("Could not find/ load wsdl url.", e);
                        System.exit(1);
                }
                long endTime = System.currentTimeMillis();
                logger.debug("Total execution took '"+(endTime-startTime)+"' milliseconds.");
                System.exit(0);
        }
        
        /**
         * Read a configuration parameter from the config file
         * Creates log messages according to parameters.
         * 
         * @param key                           property name
         * @param defaultValue          default value or null
         * @param isRequired            whether or not this is a required option
         * @param logmessage            optional log message (or null)
         * @return                                      property value
         * @throws ConfigurationException
         */
        private Object readConfProperty(String key, Object defaultValue, boolean isRequired, PropertyType type, String logmessage) throws ConfigurationException {
                Object value = null;
                switch (type) {
                case BOOLEAN:
                        value = config.getBoolean(key, (Boolean) defaultValue);
                        break;
                case STRINGARRAY:
                        value = config.getStringArray(key);
                        defaultValue = null;
                        break;

                default:
                        value = config.getString(key, (String) defaultValue);
                        break;
                }
                 
                if(isRequired && value == null) {
                        //required property     
                        if(defaultValue == null) {
                                //No value, no default
                                String s = String.format("Could not load a value for the key '%s' from the configuration file. This is a required property without a default.", key);
                                logger.error(s);
                                throw new ConfigurationException(s);
                        }
                        if(defaultValue!=null) {
                                //No value, but default
                                logger.debug(String.format("Using property value '%s' for key '%s'. This is the default value. The property is required.", value, key));
                        }
                } 
                if( (! isRequired) && value == null) {
                        //not required
                        if(value == null && defaultValue == null) {
                                //No value, no default
                                String s = String.format("Could not load a value for the key '%s' from the configuration file. This property has no default, but it is optional anyway.", key);
                                logger.warn(s);
                        }
                        if(value == null && defaultValue!=null) {
                                //No value, but default
                                logger.debug(String.format("Using property value '%s' for key '%s'. This is the default value. The property is optional.", value, key));                                
                        }
                }
                if(logmessage==null) logmessage="";
                if(key.contains("password") && value!=null ) {
                        logger.debug(String.format("Using property value '%s' for key '%s'. %s", "******", key, logmessage));
                } else {
                        if(value!=null) logger.debug(String.format("Using property value '%s' for key '%s'. %s", value, key, logmessage));
                }
                return value;
        }
        
        
        private String readConfPropertyAsString(String key, String defaultValue, boolean isRequired, String logmessage) throws ConfigurationException {
                return (String) readConfProperty(key, defaultValue, isRequired, PropertyType.STRING, logmessage);
        }
        private String[] readConfPropertyAsStringArray(String key, boolean isRequired, String logmessage) throws ConfigurationException {
                return (String[]) readConfProperty(key, null, isRequired, PropertyType.STRINGARRAY, logmessage);
        }
        private boolean readConfPropertyAsBoolean(String key, boolean defaultValue, boolean isRequired, String logmessage) throws ConfigurationException {
                Boolean b =  (Boolean) readConfProperty(key, defaultValue, isRequired, PropertyType.BOOLEAN, logmessage);
                return b.booleanValue();
        }
                
        private ALFEmitter(CommandLine cmd, long startTime) throws ConfigurationException, MalformedURLException {
                this.startTime = startTime;
                repos = cmd.getOptionValue(PARAM_REPOS);
                txn = cmd.getOptionValue(PARAM_TXN);
                rev = cmd.getOptionValue(PARAM_REV);
                String config_file = cmd.getOptionValue(PARAM_CONFIG, "emitter.properties");
                EmitterUtil.verifyFile(config_file, false, false);

                logger.debug(String.format("Using REPOS='%s' and TXN='%s'.", repos, txn));
        
                config = null;
                try {
                        config = new PropertiesConfiguration(config_file);
                } catch (ConfigurationException e) {
                        logger.error("Could not find/ load '"+config_file+"' file.", e);
                        this.exit(1);
                }
                
                /*
                 * Load Properties from Configuration file
                 */
                //it might be interesting to look into SVNKit
                //for a pure Java implementation in future
                        final String svnlook                    =       readConfPropertyAsString(OPTION_SVNLOOK, null, true, null);
                        EmitterUtil.verifyFile(svnlook, false, true);
                        final String locale                             =       readConfPropertyAsString(OPTION_LOCALE, "de_DE.UTF-8", true, null);
                        final String encoding                   =       readConfPropertyAsString(OPTION_ENCODING, "UTF-8", true, "Note that this should match your selected '"+OPTION_LOCALE+"'.");
                        
                        // Issue Id RegEx to parse commit message
                        final String[] issuepatterns = readConfPropertyAsStringArray(OPTION_ISSUEPATTERN, false, null);
                        StringBuilder sb = new StringBuilder(); for(String s : issuepatterns) sb.append(s);
                        logger.debug(String.format("Using issue id patterns: '%s'.", sb.toString()));
                        
                        // Flags to indicate what should be done
                        final boolean isSoapEnabled     = readConfPropertyAsBoolean(OPTION_IS_SOAPENABLED, true, true, null);
                        final boolean isXmlProcessingEnabled    = readConfPropertyAsBoolean(OPTION_IS_XMLPROCESSINGENABLED, true, true, null);
                        final boolean isWithVerification= readConfPropertyAsBoolean(OPTION_IS_VERIFICATIONENABLED, false, true, null);
                        final boolean isRemoveIssues    = readConfPropertyAsBoolean(OPTION_REMOVE_ISSUES_FROM_COMMIT, false, true, "");
                        final boolean isWithCommitUpdate= readConfPropertyAsBoolean(OPTION_IS_UPDATECOMMITMESSAGE, false, true, "");
                        
                        /*
                         * SVNLook phase
                         * Use svnlook to obtain information from SVN
                         */
                        SVNLookExecutor exec = new SVNLookExecutor(new File(svnlook), repos);
                        if(cmd.hasOption(PARAM_TXN)) exec.setTXN(txn);
                        if(cmd.hasOption(PARAM_REV)) exec.setRev(rev);
                        exec.setEncoding(encoding);
                        exec.setLocale(locale);
                        
                        info = exec.getCommitInfo();                    
                        info.parseIssues(issuepatterns, isRemoveIssues);
                        
                        logger.debug("SVNCommitInfo author: "+ info.getAuthor());
                        SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
                        String datestring = f.format(info.getDate());
                        datestring= datestring.substring(0, 26) + ":" + datestring.substring(26); //hack into ISO 8601 format
                        logger.debug("SVNCommitInfo date: "+ datestring);
                        logger.debug("SVNCommitInfo log message: "+ info.getLogmessage());
                        logger.debug("SVNCommitInfo file list: "+ info.getChangeFileListAsString());
                        
                        /*
                         * Verification phase
                         */
                        boolean isTrace = false;
                        if(isWithVerification) {
                                boolean isEncrypted;
                                isEncrypted                                     =       readConfPropertyAsBoolean(OPTION_SBM_ENCRYPTED, false, false, null);
                                sbm_user                                        =       readConfPropertyAsString(OPTION_SBM_USER, null, true, null);
                                sbm_pass                                        =       readConfPropertyAsString(OPTION_SBM_PASSWORD, null, false, null);
                                if(isEncrypted) sbm_pass = PasswordEncrypter.decrypt(sbm_pass);
                                endpoint                                        =       readConfPropertyAsString(OPTION_SBM_ENDPOINT, "http://localhost/gsoap/gsoap_ssl.dll?sbmappservices72", true, null);
                                querytable                                      =       readConfPropertyAsString(OPTION_SBM_TABLE, null, true, null);
                                query                                           =       readConfPropertyAsString(OPTION_SBM_QUERY, null, false, null);
                                isTrace                                         =       readConfPropertyAsBoolean(OPTION_IS_WSTRACE, false, true, null);
                        
                                logger.debug(String.format("Starting verification for '%s' issues in the list.", info.getIssues().size()));
                                boolean isOK = verify(info.getIssues(), isTrace);
                                if(! isOK ) {
                                        logger.error("Verification of issue failed. No matching issue was found.");
                                        this.exit(1);
                                }
                        } else {
                                sbm_user = null; sbm_pass=null; endpoint=null; querytable=null; query=null;
                        }

                        /*
                         * Modify original commit message
                         * using svnadmin command
                         */
                        if(isWithCommitUpdate) {
                                String svnadmin = readConfPropertyAsString(OPTION_SVNADMIN, null, true, null);
                                EmitterUtil.verifyFile(svnadmin, false, true);
                                /* If verification was turned on, we do already have the titles
                                 * so only try to load the items when verification was off
                                 */
                                if(! isWithVerification) {
                                        verify(info.getIssues(), isTrace);
                                }
                                /* Append associated items information and change log message */
                                StringBuffer newmessage = new StringBuffer();
                                newmessage.append(info.getLogmessage());
                                newmessage.append("\n\n Associated SBM items:\n");
                                for(String s : issues_titles.keySet()) {
                                        newmessage.append(s+": ");
                                        newmessage.append(issues_titles.get(s));
                                        newmessage.append("\n");
                                }
                                postCommitUpdate(new File(svnadmin), repos, locale, encoding, rev, newmessage.toString());
                        }
                        
                        /*
                         * XML processing phase
                         */                     
                        if(isXmlProcessingEnabled) {
                                
                                
                        }
                        
                
                        /*
                         * ALF Event Send phase
                         */
                        String eventmanager = null;
                        if(isSoapEnabled) {
                                eventmanager            =       readConfPropertyAsString(OPTION_EVENTMANAGER_URL, null, true, null);
                        }

                        
                        
                
                /**
                 * Event XML Erzeugen
                 */
                try {
                        String resultxml=null;
                        if(isXmlProcessingEnabled) {
                                
                                processXml();
                                addALFSecurity();
                                addEventHeader();
                                
                                // Serialize xml message to String
                                StringWriter out = new StringWriter();
                                template.getParent().serialize(out);
                                out.flush();
                                
                                resultxml =  out.getBuffer().toString();
                                if(isTrace) logger.debug("ALFEvent result:\n"+resultxml);
                
                        } else {
                                logger.debug("Xml processing is deactivated.");
                        }
                
                        if(isSoapEnabled && isXmlProcessingEnabled) {
                                final boolean isDropResponse    = readConfPropertyAsBoolean(OPTION_IS_DROPENABLED, true, true, null);
                                SimpleHttpEvent sender = new SimpleHttpEvent(eventmanager, resultxml);
                                sender.sendSoap(isDropResponse);
                                logger.debug(String.format("Sending/ receiving the soap message took '%s' milliseconds.", sender.getDuration()));
                        } else {
                                logger.warn("Sending soap message and/ or xml processing is deactivated.");
                        }
                        
                        
                } catch (FileNotFoundException e) {
                        logger.error(e.getMessage(), e);
                        this.exit(1);
                } catch (ClientProtocolException e) {
                        logger.error(e.getMessage(), e);
                        this.exit(1);
                } catch (IOException e) {
                        logger.error(e.getMessage(), e);
                        this.exit(1);
                } catch (XMLStreamException e) {
                        logger.error(e.getMessage(), e);
                        this.exit(1);
                } catch (JaxenException e) {
                        logger.error(e.getMessage(), e);
                        this.exit(1);
                } finally {
                        String forcefail = config.getString(OPTION_IS_FORCEFAILENABLED, "");
                        if(forcefail.length()>0) {
                                logger.warn("Force fail is active. All commits will be blocked.");
                                this.exit(1);
                        }
                        
                }
                
        }
                private void addEventHeader() throws JaxenException {
                        AXIOMXPath path = new AXIOMXPath("//bru1:Base/bru1:EventId");
                        path.addNamespace("bru1",  nss);
                        OMElement n = (OMElement) path.selectSingleNode(template);
                        if(n==null) {
                                logger.error("<Base> element in message is incomplete. <EventId> is missing.");
                        } else {
                                n.addChild( n.getOMFactory().createOMText("1"));
                        }
                        
                        path = new AXIOMXPath("//bru1:Base/bru1:ObjectId");
                        path.addNamespace("bru1",  nss);
                        n = (OMElement) path.selectSingleNode(template);
                        if(n==null) {
                                logger.error("<Base> element in message is incomplete. <ObjectId> is missing.");
                        } else {
                                n.addChild( n.getOMFactory().createOMText(info.getTxn() ));
                        }
                }
        
                private void addALFSecurity() throws ConfigurationException, JaxenException {
                        final String eventmanager_user  =       readConfPropertyAsString(OPTION_EVENTMANAGER_USER, null, false, null);
                        String eventmanager_pass        =       readConfPropertyAsString(OPTION_EVENTMANAGER_PASSWORD, null, false, null);
                        final boolean eventmanager_enc  =   readConfPropertyAsBoolean(OPTION_EVENTMANAGER_ENCRYPTED, false, false, null);
                        if(eventmanager_enc) eventmanager_pass = PasswordEncrypter.decrypt(eventmanager_pass);
                        
                        AXIOMXPath path = new AXIOMXPath("//bru1:User");
                        OMNamespace ns = template.findNamespace(nss, null);
                        path.addNamespace("bru1", nss);
                        OMElement n = (OMElement) path.selectSingleNode(template);
                        if(n== null) { 
                                logger.warn( String.format("<User> element was not found in namespace '%s'. Cannot add ALFSecurity elements. This is required since SBM 10.1.x.", nss));
                        } else {
                                /*
                                 *  <ns:User>
                        <!--Optional:-->
                        <ns:ALFSecurity>
                                <ns:UsernameToken>
                                <ns:Username>admin</ns:Username>
                                <ns:Password></ns:Password>
                                </ns:UsernameToken>
                        </ns:ALFSecurity>
                                        </ns:User> 
                                 */
                                n.removeChildren();
                                OMFactory fac = n.getOMFactory();
                                fac.createOMComment(n, "Generated by SVN-ALFEmitter");
                                OMElement sec = fac.createOMElement("ALFSecurity", ns);
                                OMElement token = fac.createOMElement("UsernameToken", ns);
                                OMElement user = fac.createOMElement("Username", ns);
                                user.addChild( fac.createOMText(eventmanager_user));
                                OMElement pass = fac.createOMElement("Password", ns);
                                pass.addChild(fac.createOMText(eventmanager_pass, OMNode.CDATA_SECTION_NODE));
                                token.addChild( user );
                                token.addChild( pass);
                                sec.addChild(token);
                                n.addChild(sec);                                
                        }
                }
        
                private void processXml() throws ConfigurationException, JaxenException {
                        // read additional configuration
                        final String eventtemplate              =       readConfPropertyAsString(OPTION_EVENTTEMPLATE, null, true, null);
                        EmitterUtil.verifyFile(eventtemplate, false, false);
                        try {
                                template = OMXMLBuilderFactory.createOMBuilder(new FileInputStream(new File(eventtemplate)))
                                                .getDocument().getOMDocumentElement();
                        } catch (FileNotFoundException e1) {
                                logger.error(String.format("Could not load XML event template from file '%s'.", eventtemplate), e1);
                                this.exit(1);
                        }
                        nss     =       readConfPropertyAsString(OPTION_EVENTNAMESPACE, "http://www.eclipse.org/alf/schema/EventBase/1", true, null);
                        final String marker_logmessage  =       readConfPropertyAsString(OPTION_MARKER_LOGMESSAGE, "@@logmessage@@", true, null);
                        final String marker_author              =       readConfPropertyAsString(OPTION_MARKER_AUTHOR, "@@author@@", true, null);
                        final String marker_revision    =       readConfPropertyAsString(OPTION_MARKER_REVISION, "@@revision@@", true, null);
                        final String marker_addedfiles  =       readConfPropertyAsString(OPTION_MARKER_ADDEDFILES, "@@addedfiles@@", true, null);
                        final String marker_deletedfiles =      readConfPropertyAsString(OPTION_MARKER_DELETEDFILES, "@@deletedfiles@@", true, null);
                        final String marker_changedfiles =      readConfPropertyAsString(OPTION_MARKER_CHANGEDFILES, "@@changedfiles@@", true, null);
                        final String marker_fileselementname = readConfPropertyAsString("marker.fileselementname", "file", true, null);
                        final String marker_issues              =       readConfPropertyAsString(OPTION_MARKER_ISSUES, "@@issues@@", true, null);
                        final String marker_issueselementname = readConfPropertyAsString("marker.issueselementname", "issue", true, null);
                        final String marker_internalissues    = readConfPropertyAsString(OPTION_MARKER_INTERNALISSUES, "@@internalissues@@", true, null);
        
                        SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
                        String datestring = f.format(info.getDate());
                        datestring= datestring.substring(0, 26) + ":" + datestring.substring(26); //hack into ISO 8601 format
                        
                        // add content from SVNCommitInfo object where
                        // XML commit markers are
                        addElement( marker_logmessage, info.getLogmessage(), true);
                        addElement(marker_author, info.getAuthor(), false);
                        addElement(marker_revision, info.getRev(), false);
                        addElement("@@timestamp@@", datestring, false);
                        addElements(marker_changedfiles, info.getChangedFiles(), marker_fileselementname);
                        addElements(marker_deletedfiles, info.getDeletedFiles(), marker_fileselementname);
                        addElements(marker_addedfiles, info.getAddedFiles(), marker_fileselementname);
                        addElements(marker_issues, info.getIssues(), marker_issueselementname);
                        addElements(marker_internalissues, internalissues, marker_issueselementname);
                        
                }
        
                private boolean verify(List<String> issues, boolean isTrace) throws MalformedURLException {
                
                        for(String issueid : issues) {
                                TTItemList items = getTTItems(issueid, isTrace);
                                if(items == null) {
                                        return false;
                                }
                        }
                        return true;                    
        }
                
                
                private TTItemList getTTItems(String issueid, boolean isTrace) throws MalformedURLException {
                        long startTime = System.currentTimeMillis();
                        Sbmappservices72PortType port = null;
                    JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
                    factory.setServiceClass(Sbmappservices72PortType.class);
                    factory.setAddress(endpoint);
                    port = (Sbmappservices72PortType) factory.create();
                    logger.debug("Total execution of sbmappservices72 wsdl read took '"+(System.currentTimeMillis()-startTime)+"' milliseconds.");
                 
                                        
                        if(isTrace) {
                                Client client = ClientProxy.getClient(port);
                        client.getInInterceptors().add(new LoggingInInterceptor());
                        client.getOutInterceptors().add(new LoggingOutInterceptor());
                        }

                /* -- */
                logger.debug("Total execution of sbmappservices72 initialisation took '"+(System.currentTimeMillis()-startTime)+"' milliseconds.");
                
                        ObjectFactory fac = new ObjectFactory();
                        Auth auth = fac.createAuth();
                        auth.setUserId(fac.createAuthUserId(sbm_user));
                        auth.setPassword(fac.createAuthPassword(sbm_pass));
                        
                        TableIdentifier table = fac.createTableIdentifier();
                        table.setDbName(fac.createTableIdentifierDbName(querytable));
                        
                        MultipleResponseItemOptions options = fac.createMultipleResponseItemOptions();
                        options.setSpecifiedSections(fac.createResponseItemOptionsSpecifiedSections("SECTION:FIXED"));
                        options.setSections(SectionsOption.SECTIONS_SPECIFIED);
                        
                        issueid = issueid.replaceAll("[^0-9]", "");
                        String queryWhereClause = "TS_ISSUEID = '"+issueid+"'";
                        if(query!=null && query.length()>0) queryWhereClause = queryWhereClause + " And " + query;
                        TTItemList items = null;
                        logger.debug(String.format("Using query against table '%s'. Query where clause: '%s'", querytable, queryWhereClause ));
                        try {
                                        startTime = System.currentTimeMillis();
                                        items = port.getItemsByQuery(auth, table, queryWhereClause, "", null, BigInteger.valueOf(1), options);
                                        logger.debug("Total execution of sbmappservices72 GetItems took '"+(System.currentTimeMillis()-startTime)+"' milliseconds.");
                                        
                                        //Marshaller m = JAXBContext.newInstance(TTItemList.class).createMarshaller();
                                        //m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
                                        //m.marshal( new JAXBElement<TTItemList>(new QName("uri","local"), TTItemList.class, items), System.err);
                                        
                                        logger.debug(String.format("Got Response from getItemsByQuery"));
                                        if(items!=null) {
                                                logger.debug(String.format("Verification query matched '%s' item(s) for issue '%s.'",items.getTotalCount(), issueid));
                                        }
                                        if(items.getTotalCount().intValue()<=0) {
                                                return null;
                                        } else {
                                                /* store internal ids (tableid:itemid) */
                                                internalissues.add(items.getItem().get(0).getId().getValue().getTableIdItemId().getValue());
                                                /* Store a map entry with internal id and title */
                                                issues_titles.put(items.getItem().get(0).getId().getValue().getDisplayName().getValue(), 
                                                                                        items.getItem().get(0).getTitle().getValue());
                                                
                                        }
                                        
                        } catch (AEWebservicesFaultFault e) {
                                        logger.debug("Web service fault: " + e.getFaultInfo());
                        } catch (Exception e) {
                                        logger.debug("Unknown Exception", e);
                        }
                        return items;   
                }
                
                private void postCommitUpdate(File svnadmin, String repos, String locale, String encoding, String revision, String newmessage) {
                        SVNAdminExecutor exec = new SVNAdminExecutor(svnadmin, repos);
                        exec.setRev(revision);
                        exec.setMessage(newmessage);
                        exec.setEncoding(encoding);
                        exec.setLocale(locale);
                        exec.executeSVNAdmin(SVNAdminCommand.SETLOG);                   
                }

                private void addElement(String pattern, String newCdata, boolean wrapCDATA) throws JaxenException {
                        OMComment comment = findComment(pattern);
                        if(comment!=null) {
                                OMFactory fac = OMAbstractFactory.getOMFactory();
                                int type = OMNode.TEXT_NODE;
                                if(wrapCDATA) {
                                        type = OMNode.CDATA_SECTION_NODE;
                                }
                                OMText text = fac.createOMText(newCdata, type);
                                comment.insertSiblingAfter(text);
                        }                       
                }
                
                private void addElements(String pattern, List<String> files, String elementName) throws JaxenException {
                        if(files.size()<=0) return;
                        OMComment comment = findComment(pattern);
                        if(comment!=null) {
                                OMFactory fac = OMAbstractFactory.getOMFactory();
                                OMNamespace ns = template.findNamespace(nss, null);
                                if(ns == null) logger.error(String.format("The namespace '%s' is not defined in the template.", nss));
                                for(String s : files) {
                                        OMElement e = fac.createOMElement(elementName, ns);
                                        OMText text = fac.createOMText(s, OMNode.TEXT_NODE);
                                        e.addChild(text);
                                        comment.insertSiblingAfter(e);
                                }                               
                        }
                }
                
        
                private OMComment findComment(String pattern) throws JaxenException {
                        AXIOMXPath path = new AXIOMXPath("//comment()[. = '"+pattern+"'][1]");
                        //OMNamespace ns = template.findNamespace(nss, null);
                        path.addNamespace("bru1", nss);
                        OMComment n = (OMComment) path.selectSingleNode(template);
                        if(n!=null) return n;
                        logger.warn("Comment '"+pattern+"' was not found in the XML template.");
                        return null;
                }
                
        private void exit(int errorCode) {
                long endTime = System.currentTimeMillis();
                logger.debug("Total execution took '"+(endTime-startTime)+"' milliseconds.");
                System.exit(errorCode);
        }


        private static Options getOptions() {
                Option repository = Option.builder(PARAM_REPOS)
                                .argName("repository")
                                .hasArg()
                                .longOpt("repository")
                                .desc("Path or Url to the SVN repository")
                                .required()
                                .build();

                Option txn = Option.builder(PARAM_TXN)
                                .argName("transactionid")
                                .hasArg()
                                .longOpt("transaction")
                                .desc("The SVN transaction id to examine (TXN). You cannot combine txn with -rev. "
                                                + "When a txn is given, the repository path must be a local path.")
                                .build();

                Option rev = Option.builder(PARAM_REV)
                                .argName("revision")
                                .hasArg()
                                .desc("A revision to examine. You cannot combine revision with -txn.")
                                .build();

                OptionGroup group = new OptionGroup()
                                .addOption(txn)
                                .addOption(rev);

                Option config = Option.builder(PARAM_CONFIG)
                                .argName("config_file")
                                .hasArg()
                                .longOpt("config")
                                .desc("The configuration file to use. Defaults to 'emitter.properties'.")
                                .build();

                Options options = new Options();
                options.addOption(repository);
                options.addOption(config);
                options.addOptionGroup(group);
                return options;
        }
        
        private static void printHelp() {
                // automatically generate the help statement
                HelpFormatter formatter = new HelpFormatter();
                String header = "\nSVN-ALFEventEmitter " + VERSION + ", a SVN hook implemented in Java to emit Eclipse ALFEvents on commit.\n\n";

                String footer = "Please send bug reports to bru@brutex.de.\n(c)2020 Brian Rosenberger";
                formatter.setWidth(80);
                formatter.printHelp("java -jar SVN-ALFEventEmitter", header, getOptions(), footer, true);
        }
        
        
        
        private enum PropertyType {
                STRING(), STRINGARRAY(), BOOLEAN();
        }
        
        
}