001    /*
002     * Copyright (c) 2007-2015 Concurrent, 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    
021    package cascading.property;
022    
023    import java.util.Map;
024    import java.util.Properties;
025    import java.util.Set;
026    import java.util.TreeSet;
027    
028    import cascading.util.Util;
029    import org.slf4j.Logger;
030    import org.slf4j.LoggerFactory;
031    
032    import 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     */
046    public 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          properties.put( APP_JAR_CLASS, type );
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        Class property = PropertyUtil.getProperty( properties, DEP_APP_JAR_CLASS, (Class) null );
106    
107        if( property != null )
108          {
109          LOG.warn( "using deprecated property: {}, use instead: {}", DEP_APP_JAR_CLASS, APP_JAR_CLASS );
110          return property;
111          }
112    
113        return PropertyUtil.getProperty( properties, APP_JAR_CLASS, (Class) null );
114        }
115    
116      /**
117       * Method setApplicationJarPath is used to set the application jar file.
118       * </p>
119       * All cluster executed Cascading applications
120       * need to call {@link #setApplicationJarClass(java.util.Map, Class)} or
121       * setApplicationJarPath(java.util.Map, String), otherwise ClassNotFound exceptions are likely.
122       *
123       * @param properties of type Map
124       * @param path       of type String
125       */
126      public static void setApplicationJarPath( Map<Object, Object> properties, String path )
127        {
128        if( path != null )
129          properties.put( APP_JAR_PATH, path );
130        }
131    
132      /**
133       * Method getApplicationJarPath return the path set by the setApplicationJarPath method.
134       *
135       * @param properties of type Map<Object, Object>
136       * @return String
137       */
138      public static String getApplicationJarPath( Map<Object, Object> properties )
139        {
140        String property = PropertyUtil.getProperty( properties, DEP_APP_JAR_PATH, (String) null );
141    
142        if( property != null )
143          {
144          LOG.warn( "using deprecated property: {}, use instead: {}", DEP_APP_JAR_PATH, APP_JAR_PATH );
145          return property;
146          }
147    
148        return PropertyUtil.getProperty( properties, APP_JAR_PATH, (String) null );
149        }
150    
151      public static void setApplicationID( Map<Object, Object> properties )
152        {
153        properties.put( APP_ID, getAppID() );
154        }
155    
156      public static String getApplicationID( Map<Object, Object> properties )
157        {
158        if( properties == null )
159          return getAppID();
160    
161        return PropertyUtil.getProperty( properties, APP_ID, getAppID() );
162        }
163    
164      private static String getAppID()
165        {
166        if( appID == null )
167          {
168          appID = Util.createUniqueID();
169          LOG.info( "using app.id: {}", appID );
170          }
171    
172        return appID;
173        }
174    
175      /** Sets the static appID value to null. For debugging purposes. */
176      public static void resetAppID()
177        {
178        appID = null;
179        }
180    
181      public static void setApplicationName( Map<Object, Object> properties, String name )
182        {
183        if( name != null )
184          properties.put( APP_NAME, name );
185        }
186    
187      public static String getApplicationName( Map<Object, Object> properties )
188        {
189        return PropertyUtil.getProperty( properties, APP_NAME, (String) null );
190        }
191    
192      public static void setApplicationVersion( Map<Object, Object> properties, String version )
193        {
194        if( version != null )
195          properties.put( APP_VERSION, version );
196        }
197    
198      public static String getApplicationVersion( Map<Object, Object> properties )
199        {
200        return PropertyUtil.getProperty( properties, APP_VERSION, (String) null );
201        }
202    
203      public static void addApplicationTag( Map<Object, Object> properties, String tag )
204        {
205        if( tag == null )
206          return;
207    
208        tag = tag.trim();
209    
210        if( Util.containsWhitespace( tag ) )
211          LOG.warn( "tags should not contain whitespace characters: '{}'", tag );
212    
213        String tags = PropertyUtil.getProperty( properties, APP_TAGS, (String) null );
214    
215        if( tags != null )
216          tags = join( ",", tag, tags );
217        else
218          tags = tag;
219    
220        properties.put( APP_TAGS, tags );
221        }
222    
223      public static String getApplicationTags( Map<Object, Object> properties )
224        {
225        return PropertyUtil.getProperty( properties, APP_TAGS, (String) null );
226        }
227    
228      /**
229       * Adds a framework "name:version" string to the property set and to the System properties.
230       * <p/>
231       * Properties may be null. Duplicates are removed.
232       *
233       * @param properties may be null, additionally adds to System properties
234       * @param framework  "name:version" String
235       */
236      public static void addApplicationFramework( Map<Object, Object> properties, String framework )
237        {
238        if( framework == null )
239          return;
240    
241        String frameworks = PropertyUtil.getProperty( properties, APP_FRAMEWORKS, System.getProperty( APP_FRAMEWORKS ) );
242    
243        if( frameworks != null )
244          frameworks = join( ",", framework.trim(), frameworks );
245        else
246          frameworks = framework;
247    
248        frameworks = Util.unique( frameworks, "," );
249    
250        if( properties != null )
251          properties.put( APP_FRAMEWORKS, frameworks );
252    
253        System.setProperty( APP_FRAMEWORKS, frameworks );
254        }
255    
256      public static String getApplicationFrameworks( Map<Object, Object> properties )
257        {
258        return PropertyUtil.getProperty( properties, APP_FRAMEWORKS, System.getProperty( APP_FRAMEWORKS ) );
259        }
260    
261      public AppProps()
262        {
263        }
264    
265      /**
266       * Sets the name and version of this application.
267       *
268       * @param name    of type String
269       * @param version of type String
270       */
271      public AppProps( String name, String version )
272        {
273        this.name = name;
274        this.version = version;
275        }
276    
277      /**
278       * Method setName sets the application name.
279       * <p/>
280       * By default the application name is derived from the jar name (values before the version in most Maven
281       * compatible jars).
282       *
283       * @param name type String
284       * @return this
285       */
286      public AppProps setName( String name )
287        {
288        this.name = name;
289    
290        return this;
291        }
292    
293      /**
294       * Method setVersion sets the application version.
295       * <p/>
296       * By default the application version is derived from the jar name (values after the name in most Maven
297       * compatible jars).
298       *
299       * @param version type String
300       * @return this
301       */
302      public AppProps setVersion( String version )
303        {
304        this.version = version;
305    
306        return this;
307        }
308    
309      public String getTags()
310        {
311        return join( tags, "," );
312        }
313    
314      /**
315       * Method addTag will associate a "tag" with this application. Applications can have an unlimited number of tags.
316       * <p/>
317       * Tags allow applications to be searched and organized by management tools.
318       * <p/>
319       * Tag values are opaque, but adopting a simple convention of 'category:value' allows for complex use cases.
320       * <p/>
321       * Some recommendations for categories are:
322       * <p/>
323       * <ul>
324       * <lli>cluster: - the cluster name the application is or should be run against. A name could be logical, like QA or PROD.</lli>
325       * <lli>project: - the project name, possibly a JIRA project name this application is managed under.</lli>
326       * <lli>org: - the group, team or organization that is responsible for the application.</lli>
327       * <lli>support: - the email address of the user who should be notified of failures or issues.</lli>
328       * </ul>
329       * <p/>
330       * Note that tags should not contain whitespace characters, even though this is not an error, a warning will be
331       * issues.
332       *
333       * @param tag type String
334       * @return this
335       */
336      public AppProps addTag( String tag )
337        {
338        if( !Util.isEmpty( tag ) )
339          tags.add( tag );
340    
341        return this;
342        }
343    
344      /**
345       * Method addTags will associate the given "tags" with this application. Applications can have an unlimited number of tags.
346       * <p/>
347       * Tags allow applications to be searched and organized by management tools.
348       * <p/>
349       * Tag values are opaque, but adopting a simple convention of 'category:value' allows for complex use cases.
350       * <p/>
351       * Some recommendations for categories are:
352       * <p/>
353       * <ul>
354       * <lli>cluster: - the cluster name the application is or should be run against. A name could be logical, like QA or PROD.</lli>
355       * <lli>project: - the project name, possibly a JIRA project name this application is managed under.</lli>
356       * <lli>org: - the group, team or organization that is responsible for the application.</lli>
357       * <lli>support: - the email address of the user who should be notified of failures or issues.</lli>
358       * </ul>
359       * <p/>
360       * Note that tags should not contain whitespace characters, even though this is not an error, a warning will be
361       * issues.
362       *
363       * @param tags type String
364       * @return this
365       */
366      public AppProps addTags( String... tags )
367        {
368        for( String tag : tags )
369          addTag( tag );
370    
371        return this;
372        }
373    
374      /**
375       * Method getFrameworks returns a list of frameworks used to build this App.
376       *
377       * @return Registered frameworks
378       */
379      public String getFrameworks()
380        {
381        return join( frameworks, "," );
382        }
383    
384      /**
385       * Method addFramework adds a new framework name to the list of frameworks used.
386       * <p/>
387       * Higher level tools should register themselves, and preferably with their version,
388       * for example {@code foo-flow-builder:1.2.3}.
389       * <p/>
390       * See {@link #addFramework(String, String)}.
391       *
392       * @param framework A String
393       * @return this AppProps instance
394       */
395      public AppProps addFramework( String framework )
396        {
397        if( !Util.isEmpty( framework ) )
398          frameworks.add( framework );
399    
400        return this;
401        }
402    
403      /**
404       * Method addFramework adds a new framework name and its version to the list of frameworks used.
405       * <p/>
406       * Higher level tools should register themselves, and preferably with their version,
407       * for example {@code foo-flow-builder:1.2.3}.
408       *
409       * @param framework A String
410       * @return this AppProps instance
411       */
412      public AppProps addFramework( String framework, String version )
413        {
414        if( !Util.isEmpty( framework ) && !Util.isEmpty( version ) )
415          frameworks.add( framework + ":" + version );
416    
417        if( !Util.isEmpty( framework ) )
418          frameworks.add( framework );
419    
420        return this;
421        }
422    
423      /**
424       * Method addFrameworks adds new framework names to the list of frameworks used.
425       * <p/>
426       * Higher level tools should register themselves, and preferably with their version,
427       * for example {@code foo-flow-builder:1.2.3}.
428       *
429       * @param frameworks Strings
430       * @return this AppProps instance
431       */
432      public AppProps addFrameworks( String... frameworks )
433        {
434        for( String framework : frameworks )
435          addFramework( framework );
436    
437        return this;
438        }
439    
440      /**
441       * Method setJarClass is used to set the application jar file.
442       * </p>
443       * All cluster executed Cascading applications
444       * need to call setApplicationJarClass(java.util.Map, Class) or
445       * {@link #setApplicationJarPath(java.util.Map, String)}, otherwise ClassNotFound exceptions are likely.
446       *
447       * @param jarClass of type Class
448       */
449      public AppProps setJarClass( Class jarClass )
450        {
451        this.jarClass = jarClass;
452    
453        return this;
454        }
455    
456      /**
457       * Method setJarPath is used to set the application jar file.
458       * </p>
459       * All cluster executed Cascading applications
460       * need to call {@link #setJarClass(Class)} or
461       * setJarPath(java.util.Map, String), otherwise ClassNotFound exceptions are likely.
462       *
463       * @param jarPath of type String
464       */
465      public AppProps setJarPath( String jarPath )
466        {
467        this.jarPath = jarPath;
468    
469        return this;
470        }
471    
472      @Override
473      protected void addPropertiesTo( Properties properties )
474        {
475        setApplicationID( properties );
476        setApplicationName( properties, name );
477        setApplicationVersion( properties, version );
478        addApplicationTag( properties, getTags() );
479        addApplicationFramework( properties, getFrameworks() );
480        setApplicationJarClass( properties, jarClass );
481        setApplicationJarPath( properties, jarPath );
482        }
483      }