Package com.google.feedserver.util

Source Code of com.google.feedserver.util.BeanCliHelper

/*
* Copyright 2008 Google Inc.
*
* 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 com.google.feedserver.util;

import com.google.feedserver.client.FeedServerEntry;
import com.google.gdata.data.OtherContent;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.xml.sax.SAXException;

import java.beans.IntrospectionException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;

/**
* Uses reflection to scan registered beans for any annotation containing
* {@link Flag} or {@link ConfigFile}. It uses the "help" entry in the Flag
* decorator for flag help. In addition if you specify @ConfigFile with @Flag,
* this flag will be used to read an XML file from the filesystem containing a
* payload-in-content entry for this bean. It will read the file first then
* reads the flags. Flags supersede file content. With this class you can create
* configuration files using payload-in-content files and have them represented
* as Beans for consumption in code.
* <p>
* <br/> Boolean, Integer and String fields are supported.
*
* <br/> example: to setup --filename as a flag. in class Foo with flag help.
* </p>
* <p>
* <blockquote>
*
* <pre>
*
* public class FooBean {
*    {@literal @}Flag(help = &quot;config file for foobean&quot;)
*    {@literal @}ConfigFile
*    private String configFileName = &quot;/etc/configfile&quot;;
*    {@literal @}Flag(help = &quot;filename to access&quot;)
*    private String filename = &quot;/tmp/defaultfile&quot;;
*    public getFilename() {
*       return filename;
*    }
*   
*    public setFilename(String filename) {
*      this.filename = filename;
*    }
* }
*
* public class Foo {
*      public static void main(String[] args) {
*          FooBean fooBean = new FooBean();
*          BeanCliHelper cliHelper = new BeanCliHelper();
*          cliHelper.register(fooBean); cliHelper.parse(args);
*          System.stdout.println(&quot;Filename is &quot; + fooBean.getFilename());
*      }
* }
*
* </pre>
*
* </blockquote>
* </p>
*
* Note: classes with identical flag field names will get overwritten with the
* last one processed. TODO(rayc) support collision detection and use fully
* qualified class name for flag option.
*
* @author r@kuci.org (Ray Colline)
*
*/
public class BeanCliHelper {

  // We allow tests to set the file contents to avoid going to disk
  private static String testFileContents;

  private List<Object> beans;
  private CommandLine flags;
  private Options options;

  public BeanCliHelper() {
    beans = new ArrayList<Object>();
  }

  public void register(Object bean) {
    beans.add(bean);
  }

  /**
   * With provided command line string, populates all registered classes with
   * decorated fields with their config file then command-line values.
   *
   * @param args command-line.
   */
  public void parse(String[] args) throws ConfigurationBeanException, ParseException,
      java.text.ParseException {
    options = createOptions();
    GnuParser parser = new GnuParser();
    try {
      flags = parser.parse(options, args);
    } catch (ParseException e) {
      usage();
      throw new ConfigurationBeanException("Command line parse error.", e);
    }

    /*
     * Print help text and exit.
     */
    if (flags.hasOption("help")) {
      usage();
      System.exit(0);
    }
    populateBeansFromConfigFile();
    populateBeansFromCommandLine();
  }

  /**
   * Prints usage information.
   */
  public void usage() {
    new HelpFormatter().printHelp("Usage", options);
  }

  /**
   * Must be called after {@link GnuParser#parse(Options, String[])} and if bean
   * has {@link ConfigFile} decorator load the file that field points to and
   * populate bean. If no decorator is present we just no-op. If there is not a
   * default location for the config file in the bean, or they did not specify
   * the flag on the commandline, we no-op and assume they want to configure
   * entirely from defaults or commandline.
   */
  private void populateBeansFromConfigFile() throws ConfigurationBeanException, ParseException,
      java.text.ParseException {

    populateBeansFromCommandLine(); // we do an initial pass to pick up
    // ConfigFile flag

    // We go through all registered beans.
    for (Object bean : beans) {
      // Go through each field to find annotated fields. We only support one
      // ConfigFile per
      // bean. The first one encountered here will be used.
      for (Field field : bean.getClass().getDeclaredFields()) {
        ConfigFile configFileAnnotation = field.getAnnotation(ConfigFile.class);
        String configFileName = "";
        if (configFileAnnotation == null) {
          // no config file in this bean, we no-op.
          continue;
        }

        try {
          // retrieve the field value
          configFileName =
              (String) bean.getClass().getMethod(
                  "get" + field.getName().substring(0, 1).toUpperCase()
                      + field.getName().substring(1), (Class[]) null).invoke(bean, (Object[]) null);
          if (configFileName == null) {
            return; // There is no config file default or flag entry, we no-op.
          }

          // load the file into a string.
          String configFileContents = readFileIntoString(configFileName);

          // parse the contents into the bean.
          FeedServerEntry configEntry = new FeedServerEntry(configFileContents);
          ContentUtil contentUtil = new ContentUtil();
          contentUtil.fillBean((OtherContent) configEntry.getContent(), bean);

          // BeanUtil throws many exceptions, re-wrap them and throw our
          // exception.
        } catch (RuntimeException e) {
          throw new ConfigurationBeanException(e);
        } catch (IllegalAccessException e) {
          throw new ConfigurationBeanException(e);
        } catch (InvocationTargetException e) {
          throw new ConfigurationBeanException(e);
        } catch (NoSuchMethodException e) {
          throw new ConfigurationBeanException(e);
        } catch (IOException e) {
          throw new ConfigurationBeanException("Error reading config file " + configFileName, e);
        } catch (IntrospectionException e) {
          throw new ConfigurationBeanException(e);
        } catch (SAXException e) {
          throw new ConfigurationBeanException(e);
        } catch (ParserConfigurationException e) {
          throw new ConfigurationBeanException(e);
        }
      }
    }
  }

  /**
   * Loop through each registered class and create and parse command line
   * options for fields decorated with {@link Flag}.
   */
  private void populateBeansFromCommandLine() {

    // Go through all registered beans.
    for (Object bean : beans) {

      // Search for all fields in the bean with Flag decorator.
      for (Field field : bean.getClass().getDeclaredFields()) {
        Flag flag = field.getAnnotation(Flag.class);
        if (flag == null) {
          // not decorated, continue.
          continue;
        }
        String argName = field.getName();
        // Boolean Flags
        if (field.getType().getName().equals(Boolean.class.getName())
            || field.getType().getName().equals(Boolean.TYPE.getName())) {
          if (flags.hasOption(argName)) {
            setField(field, bean, new Boolean(true));
          } else if (flags.hasOption("no" + argName)) {
            setField(field, bean, new Boolean(false));
          }
          // Integer Flags
        } else if (field.getType().getName().equals(Integer.class.getName())
            || field.getType().getName().equals(Integer.TYPE.getName())) {
          String argValue = flags.getOptionValue(argName, null);
          if (argValue != null) {
            try {
              setField(field, bean, Integer.valueOf(argValue));
            } catch (NumberFormatException e) {
              throw new RuntimeException(e);
            }
          }
          // String Flag
        } else if (field.getType().getName().equals(String.class.getName())) {
          String argValue = flags.getOptionValue(argName, null);
          if (argValue != null) {
            setField(field, bean, argValue);
          }
          // Repeated String Flag
        } else if (field.getType().getName().equals(String[].class.getName())) {
          String[] argValues = flags.getOptionValues(argName);
          if (argValues != null) {
            setField(field, bean, argValues);
          }
        }
      }
    }
  }

  /**
   * Sets value in the supplied field's setter to the given value.
   *
   * @param field the flag field.
   * @param value the value, usually a Boolean or a String.
   * @throws RuntimeException if the field is mis-configured.
   */
  private void setField(Field field, Object bean, Object value) {
    try {
      bean.getClass().getMethod(
          "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1),
          field.getType()).invoke(bean, value);
      // Lots of errors can happen when using introspection. Most are
      // programming errors
      // so we throw RuntimeExceptions.
    } catch (IllegalArgumentException e) {
      throw new RuntimeException("Field error:" + field.getName(), e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Field error:" + field.getName(), e);
    } catch (NullPointerException e) {
      throw new RuntimeException("Field error:" + field.getName(), e);
    } catch (SecurityException e) {
      throw new RuntimeException("Field error:" + field.getName(), e);
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Field error:" + field.getName(), e);
    } catch (InvocationTargetException e) {
      throw new RuntimeException("Field error:" + field.getName(), e);
    }
  }

  /**
   * For each class registered, we extract options based on the {@link Flag}
   * decorator set on each field in the bean. We use the help argument on the
   * decorator to set command line option usage text.
   *
   * Note: classes with identical flag field names will get overwritten with the
   * last one processed. TODO(rayc) support collision detection and use fully
   * qualified class name for flag option.
   *
   * see {@link Flag} for more info.
   *
   * @return Options all command-line options registered for parsing.
   */
  private Options createOptions() {

    Options options = new Options();
    options.addOption(new Option("help", false, "Print out usage."));

    // Go through all registered beans.
    for (Object bean : beans) {

      // Go through all fields.
      for (Field field : bean.getClass().getDeclaredFields()) {
        Flag flag = field.getAnnotation(Flag.class);
        if (flag == null) {
          continue; // no decorator we move on.
        }

        // Check type we only support boolean, String and Integer.
        if ((field.getType() != Integer.class) && (field.getType() != Integer.TYPE)
            && (field.getType() != String.class) && (field.getType() != Boolean.class)
            && (field.getType() != Boolean.TYPE)) {
          throw new RuntimeException("Field: " + field.getName() + " flag type not supported");
        }

        // Create options.
        String argName = field.getName();
        if (field.getType().getName().equals(Boolean.class.getName())
            || field.getType().getName().equals(Boolean.TYPE.getName())) {
          options.addOption(new Option(argName, false, flag.help()));
          options.addOption(new Option("no" + argName, false, flag.help()));
        } else {
          options.addOption(new Option(argName, true, flag.help()));
        }
      }
    }
    return options;
  }

  /**
   * Helper that loads a file into a string.
   *
   * @param fileName properties file with rules configuration.
   * @returns a String representing the file.
   * @throws IOException if any errors are encountered reading the properties
   *         file
   */
  static String readFileIntoString(final String fileName) throws IOException {
    if (testFileContents != null) {
      return testFileContents;
    }
    File file = new File(fileName);
    byte[] fileContents = new byte[(int) file.length()];
    new BufferedInputStream(new FileInputStream(fileName)).read(fileContents);
    return new String(fileContents);
  }

  /**
   * We use this to set the config file for unit tests.
   *
   * @param contents a test payload-in-content file represented as a string.
   */
  static void setTestFileContents(String contents) {
    testFileContents = contents;
  }
}
TOP

Related Classes of com.google.feedserver.util.BeanCliHelper

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