001    /*
002     * Copyright (c) 2007-2014 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        String tags = PropertyUtil.getProperty( properties, APP_TAGS, (String) null );
209    
210        if( tags != null )
211          tags = join( ",", tag.trim(), tags );
212        else
213          tags = tag;
214    
215        properties.put( APP_TAGS, tags );
216        }
217    
218      public static String getApplicationTags( Map<Object, Object> properties )
219        {
220        return PropertyUtil.getProperty( properties, APP_TAGS, (String) null );
221        }
222    
223      /**
224       * Adds a framework "name:version" string to the property set and to the System properties.
225       * <p/>
226       * Properties may be null. Duplicates are removed.
227       *
228       * @param properties may be null, additionally adds to System properties
229       * @param framework  "name:version" String
230       */
231      public static void addApplicationFramework( Map<Object, Object> properties, String framework )
232        {
233        if( framework == null )
234          return;
235    
236        String frameworks = PropertyUtil.getProperty( properties, APP_FRAMEWORKS, System.getProperty( APP_FRAMEWORKS ) );
237    
238        if( frameworks != null )
239          frameworks = join( ",", framework.trim(), frameworks );
240        else
241          frameworks = framework;
242    
243        frameworks = Util.unique( frameworks, "," );
244    
245        if( properties != null )
246          properties.put( APP_FRAMEWORKS, frameworks );
247    
248        System.setProperty( APP_FRAMEWORKS, frameworks );
249        }
250    
251      public static String getApplicationFrameworks( Map<Object, Object> properties )
252        {
253        return PropertyUtil.getProperty( properties, APP_FRAMEWORKS, System.getProperty( APP_FRAMEWORKS ) );
254        }
255    
256      public AppProps()
257        {
258        }
259    
260      /**
261       * Sets the name and version of this application.
262       *
263       * @param name    of type String
264       * @param version of type String
265       */
266      public AppProps( String name, String version )
267        {
268        this.name = name;
269        this.version = version;
270        }
271    
272      /**
273       * Method setName sets the application name.
274       * <p/>
275       * By default the application name is derived from the jar name (values before the version in most Maven
276       * compatible jars).
277       *
278       * @param name type String
279       * @return this
280       */
281      public AppProps setName( String name )
282        {
283        this.name = name;
284    
285        return this;
286        }
287    
288      /**
289       * Method setVersion sets the application version.
290       * <p/>
291       * By default the application version is derived from the jar name (values after the name in most Maven
292       * compatible jars).
293       *
294       * @param version type String
295       * @return this
296       */
297      public AppProps setVersion( String version )
298        {
299        this.version = version;
300    
301        return this;
302        }
303    
304      public String getTags()
305        {
306        return join( tags, "," );
307        }
308    
309      /**
310       * Method addTag will associate a "tag" with this application. Applications can have an unlimited number of tags.
311       * <p/>
312       * Tags allow applications to be searched and organized by management tools.
313       * <p/>
314       * Tag values are opaque, but adopting a simple convention of 'category:value' allows for complex use cases.
315       * <p/>
316       * Some recommendations for categories are:
317       * <p/>
318       * <ul>
319       * <lli>cluster: - the cluster name the application is or should be run against. A name could be logical, like QA or PROD.</lli>
320       * <lli>project: - the project name, possibly a JIRA project name this application is managed under.</lli>
321       * <lli>org: - the group, team or organization that is responsible for the application.</lli>
322       * <lli>support: - the email address of the user who should be notified of failures or issues.</lli>
323       * </ul>
324       *
325       * @param tag type String
326       * @return this
327       */
328      public AppProps addTag( String tag )
329        {
330        if( !Util.isEmpty( tag ) )
331          tags.add( tag );
332    
333        return this;
334        }
335    
336      /**
337       * Method addTags will associate the given "tags" with this application. Applications can have an unlimited number of tags.
338       * <p/>
339       * Tags allow applications to be searched and organized by management tools.
340       * <p/>
341       * Tag values are opaque, but adopting a simple convention of 'category:value' allows for complex use cases.
342       * <p/>
343       * Some recommendations for categories are:
344       * <p/>
345       * <ul>
346       * <lli>cluster: - the cluster name the application is or should be run against. A name could be logical, like QA or PROD.</lli>
347       * <lli>project: - the project name, possibly a JIRA project name this application is managed under.</lli>
348       * <lli>org: - the group, team or organization that is responsible for the application.</lli>
349       * <lli>support: - the email address of the user who should be notified of failures or issues.</lli>
350       * </ul>
351       *
352       * @param tags type String
353       * @return this
354       */
355      public AppProps addTags( String... tags )
356        {
357        for( String tag : tags )
358          addTag( tag );
359    
360        return this;
361        }
362    
363      /**
364       * Method getFrameworks returns a list of frameworks used to build this App.
365       *
366       * @return Registered frameworks
367       */
368      public String getFrameworks()
369        {
370        return join( frameworks, "," );
371        }
372    
373      /**
374       * Method addFramework adds a new framework name to the list of frameworks used.
375       * <p/>
376       * Higher level tools should register themselves, and preferably with their version,
377       * for example {@code foo-flow-builder:1.2.3}.
378       * <p/>
379       * See {@link #addFramework(String, String)}.
380       *
381       * @param framework A String
382       * @return this AppProps instance
383       */
384      public AppProps addFramework( String framework )
385        {
386        if( !Util.isEmpty( framework ) )
387          frameworks.add( framework );
388    
389        return this;
390        }
391    
392      /**
393       * Method addFramework adds a new framework name and its version 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       *
398       * @param framework A String
399       * @return this AppProps instance
400       */
401      public AppProps addFramework( String framework, String version )
402        {
403        if( !Util.isEmpty( framework ) && !Util.isEmpty( version ) )
404          frameworks.add( framework + ":" + version );
405    
406        if( !Util.isEmpty( framework ) )
407          frameworks.add( framework );
408    
409        return this;
410        }
411    
412      /**
413       * Method addFrameworks adds new framework names to the list of frameworks used.
414       * <p/>
415       * Higher level tools should register themselves, and preferably with their version,
416       * for example {@code foo-flow-builder:1.2.3}.
417       *
418       * @param frameworks Strings
419       * @return this AppProps instance
420       */
421      public AppProps addFrameworks( String... frameworks )
422        {
423        for( String framework : frameworks )
424          addFramework( framework );
425    
426        return this;
427        }
428    
429      /**
430       * Method setJarClass is used to set the application jar file.
431       * </p>
432       * All cluster executed Cascading applications
433       * need to call setApplicationJarClass(java.util.Map, Class) or
434       * {@link #setApplicationJarPath(java.util.Map, String)}, otherwise ClassNotFound exceptions are likely.
435       *
436       * @param jarClass of type Class
437       */
438      public AppProps setJarClass( Class jarClass )
439        {
440        this.jarClass = jarClass;
441    
442        return this;
443        }
444    
445      /**
446       * Method setJarPath is used to set the application jar file.
447       * </p>
448       * All cluster executed Cascading applications
449       * need to call {@link #setJarClass(Class)} or
450       * setJarPath(java.util.Map, String), otherwise ClassNotFound exceptions are likely.
451       *
452       * @param jarPath of type String
453       */
454      public AppProps setJarPath( String jarPath )
455        {
456        this.jarPath = jarPath;
457    
458        return this;
459        }
460    
461      @Override
462      protected void addPropertiesTo( Properties properties )
463        {
464        setApplicationID( properties );
465        setApplicationName( properties, name );
466        setApplicationVersion( properties, version );
467        addApplicationTag( properties, getTags() );
468        addApplicationFramework( properties, getFrameworks() );
469        setApplicationJarClass( properties, jarClass );
470        setApplicationJarPath( properties, jarPath );
471        }
472      }