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