You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@jmeter.apache.org by ubikloadpack <gi...@git.apache.org> on 2018/11/19 21:37:10 UTC

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

GitHub user ubikloadpack opened a pull request:

    https://github.com/apache/jmeter/pull/432

    Bug 62870 / Templates : Add ability to provide parameters

    ## Description
    
    This is the implementation of a feature in enhancement list:
    
    "Templates : Add ability to provide parameters:"
    
    - https://bz.apache.org/bugzilla/show_bug.cgi?id=62870
    
    It has been implemented by members of UbikLoadPack (https://ubikloadpack.com) team (Thomas, Florent and Philippe) and is donated to Apache JMeter project since we have signed a CCLA few years ago.
    
    ## Motivation and Context
    This feature will allow providing a kind of wizard for template allowing to specify parameters when creating a new Plan:
    
    https://bz.apache.org/bugzilla/show_bug.cgi?id=62870
    
    ## How Has This Been Tested?
    
    The PR contains JUnit tests for part of the code.
    And we have performed manual testing.
    
    ## Screenshots (if appropriate):
    
    The feature is fully documented in the PR with screenshots as long as existing one (template).
    
    <img width="759" alt="screen shot 2018-11-19 at 22 34 37" src="https://user-images.githubusercontent.com/14978275/48736366-58201500-ec4b-11e8-9f88-ad8b356be891.png">
    
    ## Types of changes
    
    - New feature (non-breaking change which adds functionality)
    - No Breaking change as parameters are optional
    
    ## Checklist:
    
    - [X] My code follows the [code style][style-guide] of this project.
    - [X] I have updated the documentation accordingly.
    
    [style-guide]: https://wiki.apache.org/jmeter/CodeStyleGuidelines


You can merge this pull request into a Git repository by running:

    $ git pull https://github.com/ubikloadpack/jmeter BUG_62870

Alternatively you can review and apply these changes as the patch at:

    https://github.com/apache/jmeter/pull/432.patch

To close this pull request, make a commit to your master/trunk branch
with (at least) the following in the commit message:

    This closes #432
    
----
commit 58d37f82c7fa92f50fd7df8e6ecbb8c38d9c7934
Author: pmouawad <p....@...>
Date:   2018-11-19T21:26:20Z

    Bug 62870 / Templates : Add ability to provide parameters
    
    https://bz.apache.org/bugzilla/show_bug.cgi?id=62870
    
    Contributed by https://ubikloadpack.com

----


---

[GitHub] jmeter issue #432: Bug 62870 / Templates : Add ability to provide parameters

Posted by codecov-io <gi...@git.apache.org>.
Github user codecov-io commented on the issue:

    https://github.com/apache/jmeter/pull/432
  
    # [Codecov](https://codecov.io/gh/apache/jmeter/pull/432?src=pr&el=h1) Report
    > Merging [#432](https://codecov.io/gh/apache/jmeter/pull/432?src=pr&el=desc) into [trunk](https://codecov.io/gh/apache/jmeter/commit/cc55d68116667087e75b88a270c66cecb4d7e916?src=pr&el=desc) will **decrease** coverage by `0.03%`.
    > The diff coverage is `28.43%`.
    
    [![Impacted file tree graph](https://codecov.io/gh/apache/jmeter/pull/432/graphs/tree.svg?width=650&token=6Q7CI1wFSh&height=150&src=pr)](https://codecov.io/gh/apache/jmeter/pull/432?src=pr&el=tree)
    
    ```diff
    @@             Coverage Diff             @@
    ##             trunk     #432      +/-   ##
    ===========================================
    - Coverage     58.6%   58.57%   -0.04%     
    - Complexity   10638    10667      +29     
    ===========================================
      Files         1196     1198       +2     
      Lines        76087    76331     +244     
      Branches      7361     7399      +38     
    ===========================================
    + Hits         44594    44712     +118     
    - Misses       28986    29106     +120     
    - Partials      2507     2513       +6
    ```
    
    
    | [Impacted Files](https://codecov.io/gh/apache/jmeter/pull/432?src=pr&el=tree) | Coverage Δ | Complexity Δ | |
    |---|---|---|---|
    | [...pache/jmeter/gui/action/SelectTemplatesDialog.java](https://codecov.io/gh/apache/jmeter/pull/432/diff?src=pr&el=tree#diff-c3JjL2NvcmUvb3JnL2FwYWNoZS9qbWV0ZXIvZ3VpL2FjdGlvbi9TZWxlY3RUZW1wbGF0ZXNEaWFsb2cuamF2YQ==) | `1.58% <0%> (-1.42%)` | `1 <0> (ø)` | |
    | [src/core/org/apache/jmeter/util/TemplateUtil.java](https://codecov.io/gh/apache/jmeter/pull/432/diff?src=pr&el=tree#diff-c3JjL2NvcmUvb3JnL2FwYWNoZS9qbWV0ZXIvdXRpbC9UZW1wbGF0ZVV0aWwuamF2YQ==) | `0% <0%> (ø)` | `0 <0> (?)` | |
    | [...rg/apache/jmeter/gui/action/template/Template.java](https://codecov.io/gh/apache/jmeter/pull/432/diff?src=pr&el=tree#diff-c3JjL2NvcmUvb3JnL2FwYWNoZS9qbWV0ZXIvZ3VpL2FjdGlvbi90ZW1wbGF0ZS9UZW1wbGF0ZS5qYXZh) | `20.93% <4.28%> (+20.93%)` | `12 <2> (+12)` | :arrow_up: |
    | [...he/jmeter/gui/action/template/TemplateManager.java](https://codecov.io/gh/apache/jmeter/pull/432/diff?src=pr&el=tree#diff-c3JjL2NvcmUvb3JnL2FwYWNoZS9qbWV0ZXIvZ3VpL2FjdGlvbi90ZW1wbGF0ZS9UZW1wbGF0ZU1hbmFnZXIuamF2YQ==) | `74.41% <84.21%> (+74.41%)` | `13 <10> (+13)` | :arrow_up: |
    | [...meter/gui/action/template/TestTemplateManager.java](https://codecov.io/gh/apache/jmeter/pull/432/diff?src=pr&el=tree#diff-dGVzdC9zcmMvb3JnL2FwYWNoZS9qbWV0ZXIvZ3VpL2FjdGlvbi90ZW1wbGF0ZS9UZXN0VGVtcGxhdGVNYW5hZ2VyLmphdmE=) | `87.8% <87.8%> (ø)` | `4 <4> (?)` | |
    
    ------
    
    [Continue to review full report at Codecov](https://codecov.io/gh/apache/jmeter/pull/432?src=pr&el=continue).
    > **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
    > `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
    > Powered by [Codecov](https://codecov.io/gh/apache/jmeter/pull/432?src=pr&el=footer). Last update [cc55d68...db95633](https://codecov.io/gh/apache/jmeter/pull/432?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).



---

[GitHub] jmeter issue #432: Bug 62870 / Templates : Add ability to provide parameters

Posted by pmouawad <gi...@git.apache.org>.
Github user pmouawad commented on the issue:

    https://github.com/apache/jmeter/pull/432
  
    Hi Felix,
    You’re right.
    
    Take your time 
    
    Sorry, I am always impatient guy :)
    
    Regards 


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on a diff in the pull request:

    https://github.com/apache/jmeter/pull/432#discussion_r235114098
  
    --- Diff: bin/templates/recording.jmx ---
    @@ -90,32 +101,32 @@
           <ProxyControl guiclass="ProxyControlGui" testclass="ProxyControl" testname="HTTP(S) Test Script Recorder" enabled="false">
             <stringProp name="ProxyControlGui.port">8888</stringProp>
             <collectionProp name="ProxyControlGui.exclude_list">
    -          <stringProp name="1301401588">.*toolbar\.live\.com.*</stringProp>
               <stringProp name="1179605444">(?i).*\.(bmp|css|js|gif|ico|jpe?g|png|swf|eot|otf|ttf|mp4|woff|woff2)</stringProp>
    -          <stringProp name="1276958334">update\.microsoft\.com.*</stringProp>
    -          <stringProp name="195066122">toolbarqueries\.google\..*</stringProp>
    -          <stringProp name="-1570593883">clients.*\.google.*</stringProp>
    -          <stringProp name="339269285">api\.bing\.com.*</stringProp>
    +          <stringProp name="-88591710">www\.download\.windowsupdate\.com.*</stringProp>
    --- End diff --
    
    I don't think these changes have anything to do with the feature, would you like to commit these in an extra step?


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on a diff in the pull request:

    https://github.com/apache/jmeter/pull/432#discussion_r236800696
  
    --- Diff: src/core/org/apache/jmeter/gui/action/SelectTemplatesDialog.java ---
    @@ -137,67 +160,138 @@ private void checkDirtyAndLoad(final ActionEvent actionEvent)
             if (template == null) {
                 return;
             }
    +        templateList.setValues(TemplateManager.getInstance().reset().getTemplateNames()); // reload the templates before loading
    +        
             final boolean isTestPlan = template.isTestPlan();
             // Check if the user wants to drop any changes
    -        if (isTestPlan) {
    -            ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.CHECK_DIRTY));
    -            GuiPackage guiPackage = GuiPackage.getInstance();
    -            if (guiPackage.isDirty()) {
    -                // Check if the user wants to create from template
    -                int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(),
    -                        JMeterUtils.getResString("cancel_new_from_template"), // $NON-NLS-1$
    -                        JMeterUtils.getResString("template_load?"),  // $NON-NLS-1$
    -                        JOptionPane.YES_NO_CANCEL_OPTION,
    -                        JOptionPane.QUESTION_MESSAGE);
    -                if(response == JOptionPane.YES_OPTION) {
    -                    ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.SAVE));
    -                }
    -                if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.CANCEL_OPTION) {
    -                    return; // Don't clear the plan
    -                }
    -            }
    +        if (isTestPlan && !checkDirty(actionEvent)) {
    +            return;
             }
             ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.STOP_THREAD));
             final File parent = template.getParent();
    -        final File fileToCopy = parent != null 
    +        File fileToCopy = parent != null 
                   ? new File(parent, template.getFileName())
    -              : new File(JMeterUtils.getJMeterHome(), template.getFileName());       
    -        Load.loadProjectFile(actionEvent, fileToCopy, !isTestPlan, false);
    -        this.setVisible(false);
    +              : new File(JMeterUtils.getJMeterHome(), template.getFileName());
    +        replaceTemplateParametersAndLoad(actionEvent, template, isTestPlan, fileToCopy);
    +    }
    +
    +    /**
    +     * @param actionEvent {@link ActionEvent}
    +     * @param template {@link Template} definition
    +     * @param isTestPlan If it's a full test plan or a part
    +     * @param templateFile Template file to load
    +     */
    +    void replaceTemplateParametersAndLoad(final ActionEvent actionEvent, final Template template,
    +            final boolean isTestPlan, File templateFile) {
    +        File temporaryGeneratedFile = null;
    +        try {
    +            // handle customized templates (the .jmx.fmkr files)
    +            if (template.getParameters() != null && !template.getParameters().isEmpty()) {
    +                File jmxFile = new File(templateFile.getAbsolutePath());
    +                Map<String, String> userParameters = getUserParameters();
    +                Configuration templateCfg = TemplateUtil.getTemplateConfig();
    +                try {
    +                    temporaryGeneratedFile = File.createTempFile(template.getName(), ".output");
    +                    templateFile = temporaryGeneratedFile;
    +                    TemplateUtil.processTemplate(jmxFile, temporaryGeneratedFile, templateCfg, userParameters);
    +                } catch (IOException | TemplateException ex) {
    +                    log.error("Error generating output file {} from template {}", temporaryGeneratedFile, jmxFile, ex);
    +                    return;
    +                }
    +            }
    +            Load.loadProjectFile(actionEvent, templateFile, !isTestPlan, false);
    +            this.dispose();
    +        } finally {
    +            if (temporaryGeneratedFile != null && !temporaryGeneratedFile.delete()) {
    +                log.warn("Could not delete generated output file {} from template {}", temporaryGeneratedFile, templateFile);
    +            }
    +        }
    +    }
    +
    +    /**
    +     * @param actionEvent {@link ActionEvent}
    +     * @return true if plan is not dirty or has been saved 
    +     */
    +    boolean checkDirty(final ActionEvent actionEvent) {
    --- End diff --
    
    Any reason for not using `private`?


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on a diff in the pull request:

    https://github.com/apache/jmeter/pull/432#discussion_r235125307
  
    --- Diff: src/core/org/apache/jmeter/gui/action/template/TemplateManager.java ---
    @@ -149,19 +109,107 @@ public TemplateManager reset() {
                         } else {
                             if (log.isWarnEnabled()) {
                                 log.warn("Ignoring template file:'{}' as it does not exist or is not readable",
    -                                    f.getAbsolutePath());
    +                                    file.getAbsolutePath());
                             }
                         }
                     } catch(Exception ex) {
                         if (log.isWarnEnabled()) {
    -                        log.warn("Ignoring template file:'{}', an error occurred parsing the file", f.getAbsolutePath(),
    +                        log.warn("Ignoring template file:'{}', an error occurred parsing the file", file.getAbsolutePath(),
                                     ex);
                         }
                     } 
                 }
             }
             return temps;
         }
    +    
    +    public final class LoggingErrorHandler implements ErrorHandler {
    +        private Logger logger;
    +
    +        public LoggingErrorHandler(Logger logger) {
    +            this.logger = logger;
    +        }
    +        @Override
    +        public void error(SAXParseException ex) throws SAXException {
    +            throw ex;
    +        }
    +
    +        @Override
    +        public void fatalError(SAXParseException ex) throws SAXException {
    +            throw ex;
    +        }
    +
    +        @Override
    +        public void warning(SAXParseException ex) throws SAXException {
    +            logger.warn("Warning", ex);
    +        }
    +    }
    +    
    +    public static class DefaultEntityResolver implements EntityResolver {
    +        public DefaultEntityResolver() {
    +            super();
    +        }
    +
    +        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
    +            if(systemId.endsWith("templates.dtd")) {
    +                return new InputSource(TemplateManager.class.getResourceAsStream("/org/apache/jmeter/gui/action/template/templates.dtd"));
    +            } else {
    +                return null;
    +            }
    +        }
    +    }
    +
    +    public Map<String, Template> parseTemplateFile(File file) throws IOException, SAXException, ParserConfigurationException{
    +        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    +        dbf.setValidating(true);
    +        dbf.setNamespaceAware(true);
    +        dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
    +        dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    +        DocumentBuilder bd = dbf.newDocumentBuilder();
    +        bd.setEntityResolver(new DefaultEntityResolver());
    +        LoggingErrorHandler errorHandler = new LoggingErrorHandler(log);
    --- End diff --
    
    We could hand the filename to the error handler, so that it could be used in the error messages.


---

[GitHub] jmeter issue #432: Bug 62870 / Templates : Add ability to provide parameters

Posted by pmouawad <gi...@git.apache.org>.
Github user pmouawad commented on the issue:

    https://github.com/apache/jmeter/pull/432
  
    Hello,
    I'll be merging it today.
    
    Regards


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on a diff in the pull request:

    https://github.com/apache/jmeter/pull/432#discussion_r235121173
  
    --- Diff: src/core/org/apache/jmeter/gui/action/SelectTemplatesDialog.java ---
    @@ -207,19 +290,35 @@ private void init() { // WARNING: called from ctor so must not be overridden (i.
         public void actionPerformed(ActionEvent e) {
             final Object source = e.getSource();
             if (source == cancelButton) {
    -            this.setVisible(false);
    -            return;
    +            resetJDialog();
    +            this.dispose();
             } else if (source == applyTemplateButton) {
    -            checkDirtyAndLoad(e);            
    -        } else if (source == reloadTemplateButton) {
    -            templateList.setValues(TemplateManager.getInstance().reset().getTemplateNames());
    +            String selectedTemplate = templateList.getText();
    +            Template template = TemplateManager.getInstance().getTemplateByName(selectedTemplate);
    +            if(template.getParameters() != null && !template.getParameters().isEmpty()) {
    --- End diff --
    
    I find negations harder to read. I would prefer to use `params == null || params.isEmpty()` and inverse the code blocks and/or introduce a private method `isEmpty(template.getParameters)`.


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on a diff in the pull request:

    https://github.com/apache/jmeter/pull/432#discussion_r235128465
  
    --- Diff: src/core/org/apache/jmeter/util/TemplateUtil.java ---
    @@ -0,0 +1,85 @@
    +/*
    + * Licensed to the Apache Software Foundation (ASF) under one or more
    + * contributor license agreements.  See the NOTICE file distributed with
    + * this work for additional information regarding copyright ownership.
    + * The ASF licenses this file to You 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 org.apache.jmeter.util;
    +
    +import java.io.BufferedWriter;
    +import java.io.File;
    +import java.io.FileOutputStream;
    +import java.io.IOException;
    +import java.io.OutputStreamWriter;
    +import java.io.Writer;
    +import java.nio.charset.StandardCharsets;
    +import java.util.Map;
    +
    +import freemarker.template.Configuration;
    +import freemarker.template.TemplateException;
    +import freemarker.template.TemplateExceptionHandler;
    +
    +/**
    + * Class used to process freemarkers templates
    + * @since 5.1
    + */
    +public final class TemplateUtil {
    +    
    +    private static Configuration templateConfiguration = init();
    +    
    +    private TemplateUtil() {
    +        super();
    +    }
    +    
    +    private static Configuration init() {
    +        Configuration templateConfiguration = new Configuration(Configuration.getVersion());
    +        templateConfiguration.setDefaultEncoding(StandardCharsets.UTF_8.name());
    +        templateConfiguration.setInterpolationSyntax(Configuration.SQUARE_BRACKET_INTERPOLATION_SYNTAX);
    +        templateConfiguration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
    +        return templateConfiguration;
    +    }
    +
    +    /**
    +     * Give a basic templateConfiguration
    +     * @return a Configuration
    +     */
    +    public static Configuration getTemplateConfig() {
    +        return templateConfiguration;
    +    }
    +    
    +    /**
    +     * Process a given freemarker template and put its result in a new folder.
    +     * 
    +     * @param template : file that contains the freemarker template to process
    --- End diff --
    
    Is the colon needed here? What does the rendered javadoc look like? 


---

[GitHub] jmeter issue #432: Bug 62870 / Templates : Add ability to provide parameters

Posted by pmouawad <gi...@git.apache.org>.
Github user pmouawad commented on the issue:

    https://github.com/apache/jmeter/pull/432
  
    Hello Felix, 
    I have committed the fixes following your review.
    Let me know if you will commit the PR (which I would prefer), otherwise I'll do it.
    
    Thanks
    Regards


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on a diff in the pull request:

    https://github.com/apache/jmeter/pull/432#discussion_r235118443
  
    --- Diff: src/core/org/apache/jmeter/gui/action/SelectTemplatesDialog.java ---
    @@ -137,67 +160,127 @@ private void checkDirtyAndLoad(final ActionEvent actionEvent)
             if (template == null) {
                 return;
             }
    +        templateList.setValues(TemplateManager.getInstance().reset().getTemplateNames()); // reload the templates before loading
    +        
             final boolean isTestPlan = template.isTestPlan();
             // Check if the user wants to drop any changes
    -        if (isTestPlan) {
    -            ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.CHECK_DIRTY));
    -            GuiPackage guiPackage = GuiPackage.getInstance();
    -            if (guiPackage.isDirty()) {
    -                // Check if the user wants to create from template
    -                int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(),
    -                        JMeterUtils.getResString("cancel_new_from_template"), // $NON-NLS-1$
    -                        JMeterUtils.getResString("template_load?"),  // $NON-NLS-1$
    -                        JOptionPane.YES_NO_CANCEL_OPTION,
    -                        JOptionPane.QUESTION_MESSAGE);
    -                if(response == JOptionPane.YES_OPTION) {
    -                    ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.SAVE));
    -                }
    -                if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.CANCEL_OPTION) {
    -                    return; // Don't clear the plan
    -                }
    -            }
    +        if (isTestPlan && !checkDirty(actionEvent)) {
    +            return;
             }
             ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.STOP_THREAD));
             final File parent = template.getParent();
    -        final File fileToCopy = parent != null 
    +        File fileToCopy = parent != null 
                   ? new File(parent, template.getFileName())
    -              : new File(JMeterUtils.getJMeterHome(), template.getFileName());       
    -        Load.loadProjectFile(actionEvent, fileToCopy, !isTestPlan, false);
    -        this.setVisible(false);
    +              : new File(JMeterUtils.getJMeterHome(), template.getFileName());
    +        File temporaryGeneratedFile = null;
    +        try {
    +            // handle customized templates (the .jmx.fmkr files)
    +            if (template.getParameters() != null && !template.getParameters().isEmpty()) {
    +                File jmxFile = new File(fileToCopy.getAbsolutePath());
    +                Map<String, String> userParameters = getUserParameters();
    +                Configuration templateCfg = TemplateUtil.getTemplateConfig();
    +                try {
    +                    temporaryGeneratedFile = File.createTempFile(template.getName(), ".output");
    +                    fileToCopy = temporaryGeneratedFile;
    +                    TemplateUtil.processTemplate(jmxFile, temporaryGeneratedFile, templateCfg, userParameters);
    +                } catch (IOException | TemplateException ex) {
    +                    log.error("Error generating output file {} from template {}", temporaryGeneratedFile, jmxFile, ex);
    +                    return;
    +                }
    +            }
    +            Load.loadProjectFile(actionEvent, fileToCopy, !isTestPlan, false);
    +            this.dispose();
    +        } finally {
    +            if(temporaryGeneratedFile != null && !temporaryGeneratedFile.delete()) {
    +                log.warn("Could not delete generated output file {} from template {}", temporaryGeneratedFile, fileToCopy);
    +            }
    +        }
    +    }
    +
    +    /**
    +     * @param actionEvent
    +     * @return true if we can continue
    +     */
    +    boolean checkDirty(final ActionEvent actionEvent) {
    +        ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.CHECK_DIRTY));
    +        GuiPackage guiPackage = GuiPackage.getInstance();
    +        if (guiPackage.isDirty()) {
    +            // Check if the user wants to create from template
    +            int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(),
    +                    JMeterUtils.getResString("cancel_new_from_template"), // $NON-NLS-1$
    +                    JMeterUtils.getResString("template_load?"),  // $NON-NLS-1$
    +                    JOptionPane.YES_NO_CANCEL_OPTION,
    +                    JOptionPane.QUESTION_MESSAGE);
    +            if(response == JOptionPane.YES_OPTION) {
    +                ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.SAVE));
    +                return true;
    +            }
    +            if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.CANCEL_OPTION) {
    +                return false; // Don't clear the plan
    +            }
    +        }
    +        return true;
    +    }
    +    
    +    private Map<String, String> getUserParameters(){
    +        Map<String, String> userParameters = new LinkedHashMap<>();
    +        for(Entry<String, JLabeledTextField> entry : parametersTextFields.entrySet()) {
    --- End diff --
    
    `for` is not a function, so - for me - there is a space missing in between `for` and the parentheses.


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on a diff in the pull request:

    https://github.com/apache/jmeter/pull/432#discussion_r236801357
  
    --- Diff: src/core/org/apache/jmeter/gui/action/SelectTemplatesDialog.java ---
    @@ -137,67 +160,138 @@ private void checkDirtyAndLoad(final ActionEvent actionEvent)
             if (template == null) {
                 return;
             }
    +        templateList.setValues(TemplateManager.getInstance().reset().getTemplateNames()); // reload the templates before loading
    +        
             final boolean isTestPlan = template.isTestPlan();
             // Check if the user wants to drop any changes
    -        if (isTestPlan) {
    -            ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.CHECK_DIRTY));
    -            GuiPackage guiPackage = GuiPackage.getInstance();
    -            if (guiPackage.isDirty()) {
    -                // Check if the user wants to create from template
    -                int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(),
    -                        JMeterUtils.getResString("cancel_new_from_template"), // $NON-NLS-1$
    -                        JMeterUtils.getResString("template_load?"),  // $NON-NLS-1$
    -                        JOptionPane.YES_NO_CANCEL_OPTION,
    -                        JOptionPane.QUESTION_MESSAGE);
    -                if(response == JOptionPane.YES_OPTION) {
    -                    ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.SAVE));
    -                }
    -                if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.CANCEL_OPTION) {
    -                    return; // Don't clear the plan
    -                }
    -            }
    +        if (isTestPlan && !checkDirty(actionEvent)) {
    +            return;
             }
             ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.STOP_THREAD));
             final File parent = template.getParent();
    -        final File fileToCopy = parent != null 
    +        File fileToCopy = parent != null 
                   ? new File(parent, template.getFileName())
    -              : new File(JMeterUtils.getJMeterHome(), template.getFileName());       
    -        Load.loadProjectFile(actionEvent, fileToCopy, !isTestPlan, false);
    -        this.setVisible(false);
    +              : new File(JMeterUtils.getJMeterHome(), template.getFileName());
    +        replaceTemplateParametersAndLoad(actionEvent, template, isTestPlan, fileToCopy);
    +    }
    +
    +    /**
    +     * @param actionEvent {@link ActionEvent}
    +     * @param template {@link Template} definition
    +     * @param isTestPlan If it's a full test plan or a part
    +     * @param templateFile Template file to load
    +     */
    +    void replaceTemplateParametersAndLoad(final ActionEvent actionEvent, final Template template,
    +            final boolean isTestPlan, File templateFile) {
    +        File temporaryGeneratedFile = null;
    +        try {
    +            // handle customized templates (the .jmx.fmkr files)
    +            if (template.getParameters() != null && !template.getParameters().isEmpty()) {
    +                File jmxFile = new File(templateFile.getAbsolutePath());
    +                Map<String, String> userParameters = getUserParameters();
    +                Configuration templateCfg = TemplateUtil.getTemplateConfig();
    +                try {
    +                    temporaryGeneratedFile = File.createTempFile(template.getName(), ".output");
    +                    templateFile = temporaryGeneratedFile;
    +                    TemplateUtil.processTemplate(jmxFile, temporaryGeneratedFile, templateCfg, userParameters);
    +                } catch (IOException | TemplateException ex) {
    +                    log.error("Error generating output file {} from template {}", temporaryGeneratedFile, jmxFile, ex);
    +                    return;
    +                }
    +            }
    +            Load.loadProjectFile(actionEvent, templateFile, !isTestPlan, false);
    +            this.dispose();
    +        } finally {
    +            if (temporaryGeneratedFile != null && !temporaryGeneratedFile.delete()) {
    +                log.warn("Could not delete generated output file {} from template {}", temporaryGeneratedFile, templateFile);
    +            }
    +        }
    +    }
    +
    +    /**
    +     * @param actionEvent {@link ActionEvent}
    +     * @return true if plan is not dirty or has been saved 
    --- End diff --
    
    Somehow I think a method `checkDirty` would return `true` if plan is *dirty* and has not been saved. Can you think of a better name?


---

[GitHub] jmeter issue #432: Bug 62870 / Templates : Add ability to provide parameters

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on the issue:

    https://github.com/apache/jmeter/pull/432
  
    This seems to be quite a big patch. Do you think two days are enough to have a good look at it?


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on a diff in the pull request:

    https://github.com/apache/jmeter/pull/432#discussion_r235127891
  
    --- Diff: src/core/org/apache/jmeter/resources/messages.properties ---
    @@ -1206,6 +1206,7 @@ teardown_on_shutdown=Run tearDown Thread Groups after shutdown of main threads
     template_choose=Select Template
     template_create_from=Create
     template_field=Template ($i$ where i is capturing group number, starts at 1):
    +template_fill_parameters=Fill your parameters \:
    --- End diff --
    
    In English typography there is no space before the colon :)


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on a diff in the pull request:

    https://github.com/apache/jmeter/pull/432#discussion_r235125815
  
    --- Diff: src/core/org/apache/jmeter/gui/action/template/TemplateManager.java ---
    @@ -149,19 +109,107 @@ public TemplateManager reset() {
                         } else {
                             if (log.isWarnEnabled()) {
                                 log.warn("Ignoring template file:'{}' as it does not exist or is not readable",
    -                                    f.getAbsolutePath());
    +                                    file.getAbsolutePath());
                             }
                         }
                     } catch(Exception ex) {
                         if (log.isWarnEnabled()) {
    -                        log.warn("Ignoring template file:'{}', an error occurred parsing the file", f.getAbsolutePath(),
    +                        log.warn("Ignoring template file:'{}', an error occurred parsing the file", file.getAbsolutePath(),
                                     ex);
                         }
                     } 
                 }
             }
             return temps;
         }
    +    
    +    public final class LoggingErrorHandler implements ErrorHandler {
    +        private Logger logger;
    +
    +        public LoggingErrorHandler(Logger logger) {
    +            this.logger = logger;
    +        }
    +        @Override
    +        public void error(SAXParseException ex) throws SAXException {
    +            throw ex;
    +        }
    +
    +        @Override
    +        public void fatalError(SAXParseException ex) throws SAXException {
    +            throw ex;
    +        }
    +
    +        @Override
    +        public void warning(SAXParseException ex) throws SAXException {
    +            logger.warn("Warning", ex);
    +        }
    +    }
    +    
    +    public static class DefaultEntityResolver implements EntityResolver {
    +        public DefaultEntityResolver() {
    +            super();
    +        }
    +
    +        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
    +            if(systemId.endsWith("templates.dtd")) {
    +                return new InputSource(TemplateManager.class.getResourceAsStream("/org/apache/jmeter/gui/action/template/templates.dtd"));
    +            } else {
    +                return null;
    +            }
    +        }
    +    }
    +
    +    public Map<String, Template> parseTemplateFile(File file) throws IOException, SAXException, ParserConfigurationException{
    +        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    +        dbf.setValidating(true);
    +        dbf.setNamespaceAware(true);
    +        dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
    +        dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    +        DocumentBuilder bd = dbf.newDocumentBuilder();
    +        bd.setEntityResolver(new DefaultEntityResolver());
    +        LoggingErrorHandler errorHandler = new LoggingErrorHandler(log);
    +        bd.setErrorHandler(errorHandler);
    +        Document document = bd.parse(file.getAbsolutePath());
    +        document.getDocumentElement().normalize();
    +        Map<String, Template> templates = new TreeMap<>();
    +        NodeList templateNodes = document.getElementsByTagName("template");
    +        for (int i = 0; i < templateNodes.getLength(); i++) {
    +            Node node = templateNodes.item(i);
    +            parseTemplateNode(templates, node);
    +        }
    +        return templates;
    +    }
    +
    +    /**
    +     * @param templates
    --- End diff --
    
    either fill in the javadoc, or leave it out completely.


---

[GitHub] jmeter issue #432: Bug 62870 / Templates : Add ability to provide parameters

Posted by pmouawad <gi...@git.apache.org>.
Github user pmouawad commented on the issue:

    https://github.com/apache/jmeter/pull/432
  
    Hello Team,
    The PR looks good to me, tested on Mac OSX. 
    Anybody willing to commit it ?
    I can take in charge , but I would prefer somebody else reviewing it.
    If no feedback by tomorrow evening, I'll commit it.
    
    Thanks


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on a diff in the pull request:

    https://github.com/apache/jmeter/pull/432#discussion_r235119001
  
    --- Diff: src/core/org/apache/jmeter/gui/action/SelectTemplatesDialog.java ---
    @@ -207,19 +290,35 @@ private void init() { // WARNING: called from ctor so must not be overridden (i.
         public void actionPerformed(ActionEvent e) {
             final Object source = e.getSource();
             if (source == cancelButton) {
    -            this.setVisible(false);
    -            return;
    +            resetJDialog();
    +            this.dispose();
             } else if (source == applyTemplateButton) {
    -            checkDirtyAndLoad(e);            
    -        } else if (source == reloadTemplateButton) {
    -            templateList.setValues(TemplateManager.getInstance().reset().getTemplateNames());
    +            String selectedTemplate = templateList.getText();
    +            Template template = TemplateManager.getInstance().getTemplateByName(selectedTemplate);
    +            if(template.getParameters() != null && !template.getParameters().isEmpty()) {
    +                this.setContentPane(configureParametersPanel(template.getParameters()));
    +                this.revalidate();
    +            }else {
    +                checkDirtyAndLoad(e);
    +            }
    +        } else if (source == reloadTemplateButton || source == previous) {
    +            resetJDialog();
    +        } else if(source == validateButton) {
    --- End diff --
    
    Same as with `for`. Spacepolice is calling :)


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on a diff in the pull request:

    https://github.com/apache/jmeter/pull/432#discussion_r235117129
  
    --- Diff: src/core/org/apache/jmeter/gui/action/SelectTemplatesDialog.java ---
    @@ -137,67 +160,127 @@ private void checkDirtyAndLoad(final ActionEvent actionEvent)
             if (template == null) {
                 return;
             }
    +        templateList.setValues(TemplateManager.getInstance().reset().getTemplateNames()); // reload the templates before loading
    +        
             final boolean isTestPlan = template.isTestPlan();
             // Check if the user wants to drop any changes
    -        if (isTestPlan) {
    -            ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.CHECK_DIRTY));
    -            GuiPackage guiPackage = GuiPackage.getInstance();
    -            if (guiPackage.isDirty()) {
    -                // Check if the user wants to create from template
    -                int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(),
    -                        JMeterUtils.getResString("cancel_new_from_template"), // $NON-NLS-1$
    -                        JMeterUtils.getResString("template_load?"),  // $NON-NLS-1$
    -                        JOptionPane.YES_NO_CANCEL_OPTION,
    -                        JOptionPane.QUESTION_MESSAGE);
    -                if(response == JOptionPane.YES_OPTION) {
    -                    ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.SAVE));
    -                }
    -                if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.CANCEL_OPTION) {
    -                    return; // Don't clear the plan
    -                }
    -            }
    +        if (isTestPlan && !checkDirty(actionEvent)) {
    +            return;
             }
             ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.STOP_THREAD));
             final File parent = template.getParent();
    -        final File fileToCopy = parent != null 
    +        File fileToCopy = parent != null 
                   ? new File(parent, template.getFileName())
    -              : new File(JMeterUtils.getJMeterHome(), template.getFileName());       
    -        Load.loadProjectFile(actionEvent, fileToCopy, !isTestPlan, false);
    -        this.setVisible(false);
    +              : new File(JMeterUtils.getJMeterHome(), template.getFileName());
    +        File temporaryGeneratedFile = null;
    --- End diff --
    
    Could this and the following try block be extracted into a method? That would enable us to show the intent of the code block by using the method name.


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on a diff in the pull request:

    https://github.com/apache/jmeter/pull/432#discussion_r235127351
  
    --- Diff: src/core/org/apache/jmeter/gui/action/template/TemplateManager.java ---
    @@ -149,19 +109,107 @@ public TemplateManager reset() {
                         } else {
                             if (log.isWarnEnabled()) {
                                 log.warn("Ignoring template file:'{}' as it does not exist or is not readable",
    -                                    f.getAbsolutePath());
    +                                    file.getAbsolutePath());
                             }
                         }
                     } catch(Exception ex) {
                         if (log.isWarnEnabled()) {
    -                        log.warn("Ignoring template file:'{}', an error occurred parsing the file", f.getAbsolutePath(),
    +                        log.warn("Ignoring template file:'{}', an error occurred parsing the file", file.getAbsolutePath(),
                                     ex);
                         }
                     } 
                 }
             }
             return temps;
         }
    +    
    +    public final class LoggingErrorHandler implements ErrorHandler {
    +        private Logger logger;
    +
    +        public LoggingErrorHandler(Logger logger) {
    +            this.logger = logger;
    +        }
    +        @Override
    +        public void error(SAXParseException ex) throws SAXException {
    +            throw ex;
    +        }
    +
    +        @Override
    +        public void fatalError(SAXParseException ex) throws SAXException {
    +            throw ex;
    +        }
    +
    +        @Override
    +        public void warning(SAXParseException ex) throws SAXException {
    +            logger.warn("Warning", ex);
    +        }
    +    }
    +    
    +    public static class DefaultEntityResolver implements EntityResolver {
    +        public DefaultEntityResolver() {
    +            super();
    +        }
    +
    +        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
    +            if(systemId.endsWith("templates.dtd")) {
    +                return new InputSource(TemplateManager.class.getResourceAsStream("/org/apache/jmeter/gui/action/template/templates.dtd"));
    +            } else {
    +                return null;
    +            }
    +        }
    +    }
    +
    +    public Map<String, Template> parseTemplateFile(File file) throws IOException, SAXException, ParserConfigurationException{
    +        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    +        dbf.setValidating(true);
    +        dbf.setNamespaceAware(true);
    +        dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
    +        dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    +        DocumentBuilder bd = dbf.newDocumentBuilder();
    +        bd.setEntityResolver(new DefaultEntityResolver());
    +        LoggingErrorHandler errorHandler = new LoggingErrorHandler(log);
    +        bd.setErrorHandler(errorHandler);
    +        Document document = bd.parse(file.getAbsolutePath());
    +        document.getDocumentElement().normalize();
    +        Map<String, Template> templates = new TreeMap<>();
    +        NodeList templateNodes = document.getElementsByTagName("template");
    +        for (int i = 0; i < templateNodes.getLength(); i++) {
    +            Node node = templateNodes.item(i);
    +            parseTemplateNode(templates, node);
    +        }
    +        return templates;
    +    }
    +
    +    /**
    +     * @param templates
    +     * @param templateNode
    +     */
    +    void parseTemplateNode(Map<String, Template> templates, Node templateNode) {
    +        if (templateNode.getNodeType() == Node.ELEMENT_NODE) {
    +            Template template = new Template();
    +            Element element =  (Element) templateNode;
    +            template.setTestPlan("true".equals(element.getAttribute("isTestPlan")));
    +            template.setName(element.getElementsByTagName("name").item(0).getTextContent());
    --- End diff --
    
    maybe introduce a helper method, that extracts the text content for the first element found:
    `template.setName(textOfFirstTag(element, "name"))`
    This seems to be repeated three times.


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by asfgit <gi...@git.apache.org>.
Github user asfgit closed the pull request at:

    https://github.com/apache/jmeter/pull/432


---

[GitHub] jmeter pull request #432: Bug 62870 / Templates : Add ability to provide par...

Posted by FSchumacher <gi...@git.apache.org>.
Github user FSchumacher commented on a diff in the pull request:

    https://github.com/apache/jmeter/pull/432#discussion_r235117924
  
    --- Diff: src/core/org/apache/jmeter/gui/action/SelectTemplatesDialog.java ---
    @@ -137,67 +160,127 @@ private void checkDirtyAndLoad(final ActionEvent actionEvent)
             if (template == null) {
                 return;
             }
    +        templateList.setValues(TemplateManager.getInstance().reset().getTemplateNames()); // reload the templates before loading
    +        
             final boolean isTestPlan = template.isTestPlan();
             // Check if the user wants to drop any changes
    -        if (isTestPlan) {
    -            ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.CHECK_DIRTY));
    -            GuiPackage guiPackage = GuiPackage.getInstance();
    -            if (guiPackage.isDirty()) {
    -                // Check if the user wants to create from template
    -                int response = JOptionPane.showConfirmDialog(GuiPackage.getInstance().getMainFrame(),
    -                        JMeterUtils.getResString("cancel_new_from_template"), // $NON-NLS-1$
    -                        JMeterUtils.getResString("template_load?"),  // $NON-NLS-1$
    -                        JOptionPane.YES_NO_CANCEL_OPTION,
    -                        JOptionPane.QUESTION_MESSAGE);
    -                if(response == JOptionPane.YES_OPTION) {
    -                    ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.SAVE));
    -                }
    -                if (response == JOptionPane.CLOSED_OPTION || response == JOptionPane.CANCEL_OPTION) {
    -                    return; // Don't clear the plan
    -                }
    -            }
    +        if (isTestPlan && !checkDirty(actionEvent)) {
    +            return;
             }
             ActionRouter.getInstance().doActionNow(new ActionEvent(actionEvent.getSource(), actionEvent.getID(), ActionNames.STOP_THREAD));
             final File parent = template.getParent();
    -        final File fileToCopy = parent != null 
    +        File fileToCopy = parent != null 
                   ? new File(parent, template.getFileName())
    -              : new File(JMeterUtils.getJMeterHome(), template.getFileName());       
    -        Load.loadProjectFile(actionEvent, fileToCopy, !isTestPlan, false);
    -        this.setVisible(false);
    +              : new File(JMeterUtils.getJMeterHome(), template.getFileName());
    +        File temporaryGeneratedFile = null;
    +        try {
    +            // handle customized templates (the .jmx.fmkr files)
    +            if (template.getParameters() != null && !template.getParameters().isEmpty()) {
    +                File jmxFile = new File(fileToCopy.getAbsolutePath());
    +                Map<String, String> userParameters = getUserParameters();
    +                Configuration templateCfg = TemplateUtil.getTemplateConfig();
    +                try {
    +                    temporaryGeneratedFile = File.createTempFile(template.getName(), ".output");
    +                    fileToCopy = temporaryGeneratedFile;
    +                    TemplateUtil.processTemplate(jmxFile, temporaryGeneratedFile, templateCfg, userParameters);
    +                } catch (IOException | TemplateException ex) {
    +                    log.error("Error generating output file {} from template {}", temporaryGeneratedFile, jmxFile, ex);
    +                    return;
    +                }
    +            }
    +            Load.loadProjectFile(actionEvent, fileToCopy, !isTestPlan, false);
    +            this.dispose();
    +        } finally {
    +            if(temporaryGeneratedFile != null && !temporaryGeneratedFile.delete()) {
    +                log.warn("Could not delete generated output file {} from template {}", temporaryGeneratedFile, fileToCopy);
    +            }
    +        }
    +    }
    +
    +    /**
    +     * @param actionEvent
    +     * @return true if we can continue
    --- End diff --
    
    This comment seems odd to me. If it returns `true` I would think, that we found the state to be `dirty`. Why would I want to continue in such a state? 


---