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  }