    001/*
    002 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved.
    003 *
    004 * Project and contact information: http://www.cascading.org/
    005 *
    006 * This file is part of the Cascading project.
    007 *
    008 * Licensed under the Apache License, Version 2.0 (the "License");
    009 * you may not use this file except in compliance with the License.
    010 * You may obtain a copy of the License at
    011 *
    012 *     http://www.apache.org/licenses/LICENSE-2.0
    013 *
    014 * Unless required by applicable law or agreed to in writing, software
    015 * distributed under the License is distributed on an "AS IS" BASIS,
    016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    017 * See the License for the specific language governing permissions and
    018 * limitations under the License.
    019 */
    020
    021package cascading.property;
    022
    023import java.util.Map;
    024import java.util.Properties;
    025import java.util.Set;
    026import java.util.TreeSet;
    027
    028import cascading.util.Util;
    029import org.slf4j.Logger;
    030import org.slf4j.LoggerFactory;
    031
    032import static cascading.util.Util.join;
    033
    034/**
    035 * Class AppProps is a fluent helper for setting various application level properties that every
    036 * {@link cascading.flow.Flow} may or may not be required to have set. These properties are typically passed to a Flow
    037 * via a {@link cascading.flow.FlowConnector}.
    038 * <p/>
    039 * New property settings that may be set in Cascading 2 are application name, version, and any tags.
    040 * <p/>
    041 * See {@link #addTag(String)} for examples of using tags to help manage an application.
    042 * <p/>
    043 * In prior releases, the FlowConnector was responsible for setting the "application jar" class or path. Those
    044 * methods have been deprecated and moved to AppProps.
    045 */
    046public class AppProps extends Props
    047  {
    048  private static final Logger LOG = LoggerFactory.getLogger( AppProps.class );
    049
    050  public static final String APP_ID = "cascading.app.id";
    051  public static final String APP_NAME = "cascading.app.name";
    052  public static final String APP_VERSION = "cascading.app.version";
    053  public static final String APP_TAGS = "cascading.app.tags";
    054  public static final String APP_FRAMEWORKS = "cascading.app.frameworks";
    055  public static final String APP_JAR_CLASS = "cascading.app.appjar.class";
    056  public static final String APP_JAR_PATH = "cascading.app.appjar.path";
    057
    058  static final String DEP_APP_JAR_CLASS = "cascading.flowconnector.appjar.class";
    059  static final String DEP_APP_JAR_PATH = "cascading.flowconnector.appjar.path";
    060
    061  // need a global unique value here
    062  private static String appID;
    063
    064  protected String name;
    065  protected String version;
    066  protected Set<String> tags = new TreeSet<String>();
    067  protected Class jarClass;
    068  protected String jarPath;
    069  protected Set<String> frameworks = new TreeSet<String>();
    070
    071  /**
    072   * Creates a new AppProps instance.
    073   *
    074   * @return AppProps instance
    075   */
    076  public static AppProps appProps()
    077    {
    078    return new AppProps();
    079    }
    080
    081  /**
    082   * Method setApplicationJarClass is used to set the application jar file.
    083   * </p>
    084   * All cluster executed Cascading applications
    085   * need to call setApplicationJarClass(java.util.Map, Class) or
    086   * {@link #setApplicationJarPath(java.util.Map, String)}, otherwise ClassNotFound exceptions are likely.
    087   *
    088   * @param properties of type Map
    089   * @param type       of type Class
    090   */
    091  public static void setApplicationJarClass( Map<Object, Object> properties, Class type )
    092    {
    093    if( type != null )
    094      PropertyUtil.setProperty( properties, APP_JAR_CLASS, type.getName() );
    095    }
    096
    097  /**
    098   * Method getApplicationJarClass returns the Class set by the setApplicationJarClass method.
    099   *
    100   * @param properties of type Map<Object, Object>
    101   * @return Class
    102   */
    103  public static Class getApplicationJarClass( Map<Object, Object> properties )
    104    {
    105    String className = PropertyUtil.getProperty( properties, DEP_APP_JAR_CLASS, (String) null );
    106
    107    if( className != null )
    108      {
    109      LOG.warn( "using deprecated property: {}, use instead: {}", DEP_APP_JAR_CLASS, APP_JAR_CLASS );
    110      return Util.loadClassSafe( className );
    111      }
    112
    113    className = PropertyUtil.getProperty( properties, APP_JAR_CLASS, (String) null );
    114    if( className == null )
    115      return null;
    116    return Util.loadClassSafe( className );
    117    }
    118
    119  /**
    120   * Method setApplicationJarPath is used to set the application jar file.
    121   * </p>
    122   * All cluster executed Cascading applications
    123   * need to call {@link #setApplicationJarClass(java.util.Map, Class)} or
    124   * setApplicationJarPath(java.util.Map, String), otherwise ClassNotFound exceptions are likely.
    125   *
    126   * @param properties of type Map
    127   * @param path       of type String
    128   */
    129  public static void setApplicationJarPath( Map<Object, Object> properties, String path )
    130    {
    131    if( path != null )
    132      properties.put( APP_JAR_PATH, path );
    133    }
    134
    135  /**
    136   * Method getApplicationJarPath return the path set by the setApplicationJarPath method.
    137   *
    138   * @param properties of type Map<Object, Object>
    139   * @return String
    140   */
    141  public static String getApplicationJarPath( Map<Object, Object> properties )
    142    {
    143    String property = PropertyUtil.getProperty( properties, DEP_APP_JAR_PATH, (String) null );
    144
    145    if( property != null )
    146      {
    147      LOG.warn( "using deprecated property: {}, use instead: {}", DEP_APP_JAR_PATH, APP_JAR_PATH );
    148      return property;
    149      }
    150
    151    return PropertyUtil.getProperty( properties, APP_JAR_PATH, (String) null );
    152    }
    153
    154  public static void setApplicationID( Map<Object, Object> properties )
    155    {
    156    properties.put( APP_ID, getAppID() );
    157    }
    158
    159  public static String getApplicationID( Map<Object, Object> properties )
    160    {
    161    if( properties == null )
    162      return getAppID();
    163
    164    return PropertyUtil.getProperty( properties, APP_ID, getAppID() );
    165    }
    166
    167  public static String getApplicationID()
    168    {
    169    return getAppID();
    170    }
    171
    172  private static String getAppID()
    173    {
    174    if( appID == null )
    175      {
    176      appID = Util.createUniqueID();
    177      LOG.info( "using app.id: {}", appID );
    178      }
    179
    180    return appID;
    181    }
    182
    183  /** Sets the static appID value to null. For debugging purposes. */
    184  public static void resetAppID()
    185    {
    186    appID = null;
    187    }
    188
    189  public static void setApplicationName( Map<Object, Object> properties, String name )
    190    {
    191    if( name != null )
    192      properties.put( APP_NAME, name );
    193    }
    194
    195  public static String getApplicationName( Map<Object, Object> properties )
    196    {
    197    return PropertyUtil.getProperty( properties, APP_NAME, (String) null );
    198    }
    199
    200  public static void setApplicationVersion( Map<Object, Object> properties, String version )
    201    {
    202    if( version != null )
    203      properties.put( APP_VERSION, version );
    204    }
    205
    206  public static String getApplicationVersion( Map<Object, Object> properties )
    207    {
    208    return PropertyUtil.getProperty( properties, APP_VERSION, (String) null );
    209    }
    210
    211  public static void addApplicationTag( Map<Object, Object> properties, String tag )
    212    {
    213    if( tag == null )
    214      return;
    215
    216    tag = tag.trim();
    217
    218    if( Util.containsWhitespace( tag ) )
    219      LOG.warn( "tags should not contain whitespace characters: '{}'", tag );
    220
    221    String tags = PropertyUtil.getProperty( properties, APP_TAGS, (String) null );
    222
    223    if( tags != null )
    224      tags = join( ",", tag, tags );
    225    else
    226      tags = tag;
    227
    228    properties.put( APP_TAGS, tags );
    229    }
    230
    231  public static String getApplicationTags( Map<Object, Object> properties )
    232    {
    233    return PropertyUtil.getProperty( properties, APP_TAGS, (String) null );
    234    }
    235
    236  /**
    237   * Adds a framework "name:version" string to the property set and to the System properties.
    238   * <p/>
    239   * Properties may be null. Duplicates are removed.
    240   *
    241   * @param properties may be null, additionally adds to System properties
    242   * @param framework  "name:version" String
    243   */
    244  public static void addApplicationFramework( Map<Object, Object> properties, String framework )
    245    {
    246    if( framework == null )
    247      return;
    248
    249    String frameworks = PropertyUtil.getProperty( properties, APP_FRAMEWORKS, System.getProperty( APP_FRAMEWORKS ) );
    250
    251    if( frameworks != null )
    252      frameworks = join( ",", framework.trim(), frameworks );
    253    else
    254      frameworks = framework;
    255
    256    frameworks = Util.unique( frameworks, "," );
    257
    258    if( properties != null )
    259      properties.put( APP_FRAMEWORKS, frameworks );
    260
    261    System.setProperty( APP_FRAMEWORKS, frameworks );
    262    }
    263
    264  public static String getApplicationFrameworks( Map<Object, Object> properties )
    265    {
    266    return PropertyUtil.getProperty( properties, APP_FRAMEWORKS, System.getProperty( APP_FRAMEWORKS ) );
    267    }
    268
    269  public AppProps()
    270    {
    271    }
    272
    273  /**
    274   * Sets the name and version of this application.
    275   *
    276   * @param name    of type String
    277   * @param version of type String
    278   */
    279  public AppProps( String name, String version )
    280    {
    281    this.name = name;
    282    this.version = version;
    283    }
    284
    285  /**
    286   * Method setName sets the application name.
    287   * <p/>
    288   * By default the application name is derived from the jar name (values before the version in most Maven
    289   * compatible jars).
    290   *
    291   * @param name type String
    292   * @return this
    293   */
    294  public AppProps setName( String name )
    295    {
    296    this.name = name;
    297
    298    return this;
    299    }
    300
    301  /**
    302   * Method setVersion sets the application version.
    303   * <p/>
    304   * By default the application version is derived from the jar name (values after the name in most Maven
    305   * compatible jars).
    306   *
    307   * @param version type String
    308   * @return this
    309   */
    310  public AppProps setVersion( String version )
    311    {
    312    this.version = version;
    313
    314    return this;
    315    }
    316
    317  public String getTags()
    318    {
    319    return join( tags, "," );
    320    }
    321
    322  /**
    323   * Method addTag will associate a "tag" with this application. Applications can have an unlimited number of tags.
    324   * <p/>
    325   * Tags allow applications to be searched and organized by management tools.
    326   * <p/>
    327   * Tag values are opaque, but adopting a simple convention of 'category:value' allows for complex use cases.
    328   * <p/>
    329   * Some recommendations for categories are:
    330   * <p/>
    331   * <ul>
    332   * <lli>cluster: - the cluster name the application is or should be run against. A name could be logical, like QA or PROD.</lli>
    333   * <lli>project: - the project name, possibly a JIRA project name this application is managed under.</lli>
    334   * <lli>org: - the group, team or organization that is responsible for the application.</lli>
    335   * <lli>support: - the email address of the user who should be notified of failures or issues.</lli>
    336   * </ul>
    337   * <p/>
    338   * Note that tags should not contain whitespace characters, even though this is not an error, a warning will be
    339   * issues.
    340   *
    341   * @param tag type String
    342   * @return this
    343   */
    344  public AppProps addTag( String tag )
    345    {
    346    if( !Util.isEmpty( tag ) )
    347      tags.add( tag );
    348
    349    return this;
    350    }
    351
    352  /**
    353   * Method addTags will associate the given "tags" with this application. Applications can have an unlimited number of tags.
    354   * <p/>
    355   * Tags allow applications to be searched and organized by management tools.
    356   * <p/>
    357   * Tag values are opaque, but adopting a simple convention of 'category:value' allows for complex use cases.
    358   * <p/>
    359   * Some recommendations for categories are:
    360   * <p/>
    361   * <ul>
    362   * <lli>cluster: - the cluster name the application is or should be run against. A name could be logical, like QA or PROD.</lli>
    363   * <lli>project: - the project name, possibly a JIRA project name this application is managed under.</lli>
    364   * <lli>org: - the group, team or organization that is responsible for the application.</lli>
    365   * <lli>support: - the email address of the user who should be notified of failures or issues.</lli>
    366   * </ul>
    367   * <p/>
    368   * Note that tags should not contain whitespace characters, even though this is not an error, a warning will be
    369   * issues.
    370   *
    371   * @param tags type String
    372   * @return this
    373   */
    374  public AppProps addTags( String... tags )
    375    {
    376    for( String tag : tags )
    377      addTag( tag );
    378
    379    return this;
    380    }
    381
    382  /**
    383   * Method getFrameworks returns a list of frameworks used to build this App.
    384   *
    385   * @return Registered frameworks
    386   */
    387  public String getFrameworks()
    388    {
    389    return join( frameworks, "," );
    390    }
    391
    392  /**
    393   * Method addFramework adds a new framework name to the list of frameworks used.
    394   * <p/>
    395   * Higher level tools should register themselves, and preferably with their version,
    396   * for example {@code foo-flow-builder:1.2.3}.
    397   * <p/>
    398   * See {@link #addFramework(String, String)}.
    399   *
    400   * @param framework A String
    401   * @return this AppProps instance
    402   */
    403  public AppProps addFramework( String framework )
    404    {
    405    if( !Util.isEmpty( framework ) )
    406      frameworks.add( framework );
    407
    408    return this;
    409    }
    410
    411  /**
    412   * Method addFramework adds a new framework name and its version to the list of frameworks used.
    413   * <p/>
    414   * Higher level tools should register themselves, and preferably with their version,
    415   * for example {@code foo-flow-builder:1.2.3}.
    416   *
    417   * @param framework A String
    418   * @return this AppProps instance
    419   */
    420  public AppProps addFramework( String framework, String version )
    421    {
    422    if( !Util.isEmpty( framework ) && !Util.isEmpty( version ) )
    423      frameworks.add( framework + ":" + version );
    424
    425    if( !Util.isEmpty( framework ) )
    426      frameworks.add( framework );
    427
    428    return this;
    429    }
    430
    431  /**
    432   * Method addFrameworks adds new framework names to the list of frameworks used.
    433   * <p/>
    434   * Higher level tools should register themselves, and preferably with their version,
    435   * for example {@code foo-flow-builder:1.2.3}.
    436   *
    437   * @param frameworks Strings
    438   * @return this AppProps instance
    439   */
    440  public AppProps addFrameworks( String... frameworks )
    441    {
    442    for( String framework : frameworks )
    443      addFramework( framework );
    444
    445    return this;
    446    }
    447
    448  /**
    449   * Method setJarClass is used to set the application jar file.
    450   * </p>
    451   * All cluster executed Cascading applications
    452   * need to call setApplicationJarClass(java.util.Map, Class) or
    453   * {@link #setApplicationJarPath(java.util.Map, String)}, otherwise ClassNotFound exceptions are likely.
    454   *
    455   * @param jarClass of type Class
    456   */
    457  public AppProps setJarClass( Class jarClass )
    458    {
    459    this.jarClass = jarClass;
    460
    461    return this;
    462    }
    463
    464  /**
    465   * Method setJarPath is used to set the application jar file.
    466   * </p>
    467   * All cluster executed Cascading applications
    468   * need to call {@link #setJarClass(Class)} or
    469   * setJarPath(java.util.Map, String), otherwise ClassNotFound exceptions are likely.
    470   *
    471   * @param jarPath of type String
    472   */
    473  public AppProps setJarPath( String jarPath )
    474    {
    475    this.jarPath = jarPath;
    476
    477    return this;
    478    }
    479
    480  @Override
    481  protected void addPropertiesTo( Properties properties )
    482    {
    483    setApplicationID( properties );
    484    setApplicationName( properties, name );
    485    setApplicationVersion( properties, version );
    486    addApplicationTag( properties, getTags() );
    487    addApplicationFramework( properties, getFrameworks() );
    488    setApplicationJarClass( properties, jarClass );
    489    setApplicationJarPath( properties, jarPath );
    490    }
    491  }