1   package com.atlassian.pageobjects.elements.timeout;
2   
3   import java.io.FileInputStream;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.util.Map;
7   import java.util.Properties;
8   
9   import com.google.common.collect.Maps;
10  
11  import org.apache.commons.io.IOUtils;
12  import org.hamcrest.StringDescription;
13  import org.slf4j.Logger;
14  import org.slf4j.LoggerFactory;
15  
16  import static com.atlassian.pageobjects.elements.util.StringConcat.asString;
17  import static com.google.common.base.Preconditions.checkArgument;
18  import static com.google.common.base.Preconditions.checkNotNull;
19  
20  /**
21   * <p>
22   * {@link com.atlassian.pageobjects.elements.timeout.Timeouts} implementation based on Java properties.
23   *
24   * <p>
25   * This implementation accepts a {@link java.util.Properties} instance that contains properties in the following
26   * form: 'com.atlassian.timeout.&lt;TIMEOUT_TYPE&gt;, where &lt;TIMEOUT_TYPE&gt; corresponds to a particular
27   * field name of the {@link com.atlassian.pageobjects.elements.timeout.TimeoutType} enum.
28   *
29   * <p>
30   * At the very least, the properties are supposed to contain the {@link com.atlassian.pageobjects.elements.timeout.TimeoutType#DEFAULT}
31   * value (which corresponds to the property key 'com.atlassian.timeout.DEFAULT'). If it is not present, an exception
32   * will be raised from the constructor. This value will be used in place of whatever other timeout type that
33   * does have corresponding value within the properties.
34   *
35   */
36  public class PropertiesBasedTimeouts implements Timeouts
37  {
38      private static final Logger log = LoggerFactory.getLogger(PropertiesBasedTimeouts.class);
39  
40      public static final String PROPERTY_PREFIX = asString("com.atlassian.timeout.");
41      public static final String DEFAULT_PROPERTY_KEY = propKey(TimeoutType.DEFAULT);
42  
43      /**
44       * Load instance of <tt>PropertiesBasedTimeouts</tt> based on properties from file on disk.
45       *
46       * @param path path of the properties file
47       * @return new instance of this class
48       */
49      public static PropertiesBasedTimeouts fromFile(String path)
50      {
51          return new PropertiesBasedTimeouts(loadFromFile(path));
52      }
53  
54      /**
55       * Load instance of <tt>PropertiesBasedTimeouts</tt> based on properties from a class path resource.
56       *
57       * @param path path of the resource
58       * @param loader class loader to use
59       * @return new instance of this class
60       */
61      public static PropertiesBasedTimeouts fromClassPath(String path, ClassLoader loader)
62      {
63          InputStream is = loader.getResourceAsStream(path);
64          try
65          {
66              return new PropertiesBasedTimeouts(checkNotNull(is));
67          }
68          finally
69          {
70              IOUtils.closeQuietly(is);
71          }
72      }
73  
74      /**
75       * Load instance of <tt>PropertiesBasedTimeouts</tt> based on properties from a class path resource.
76       * The class loader that loaded this class will be used.
77       *
78       * @param path path of the resource
79       * @return new instance of this class
80       */
81      public static PropertiesBasedTimeouts fromClassPath(String path)
82      {
83          return fromClassPath(path, PropertiesBasedTimeouts.class.getClassLoader());
84      }
85  
86      private static String propKey(TimeoutType timeoutType)
87      {
88          return PROPERTY_PREFIX + timeoutType.toString();
89      }
90  
91      private final Map<String,String> properties;
92      private final long defaultValue;
93  
94      public PropertiesBasedTimeouts(final Properties properties)
95      {
96          this.properties = Maps.fromProperties(checkNotNull(properties));
97          defaultValue = validateAndGetDefault();
98      }
99  
100     /**
101      * Reads the properties from given <tt>reader</tt>. The reader <b>will not</b> be closed.
102      *
103      * @param reader reader to load properties from
104      */
105     public PropertiesBasedTimeouts(final InputStream reader)
106     {
107         this(loadFromReader(reader));
108     }
109 
110     private static Properties loadFromReader(InputStream reader)
111     {
112         try
113         {
114             Properties answer = new Properties();
115             answer.load(reader);
116             return answer;
117         }
118         catch (IOException e)
119         {
120             throw new IllegalArgumentException("Unable to read from path <" + reader + ">", e);
121         }
122     }
123 
124     private static Properties loadFromFile(String path)
125     {
126         InputStream reader = null;
127         try
128         {
129             reader = new FileInputStream(path);
130             Properties answer = new Properties();
131             answer.load(reader);
132             return answer;
133 
134         }
135         catch (IOException e)
136         {
137             throw new IllegalArgumentException("Unable to read from path <" + path + ">", e);
138         }
139         finally
140         {
141             IOUtils.closeQuietly(reader);
142         }
143     }
144 
145     private long validateAndGetDefault()
146     {
147         checkArgument(properties.containsKey(DEFAULT_PROPERTY_KEY), "Must contain default timeout property with key <"
148                 + DEFAULT_PROPERTY_KEY + ">");
149         return Long.parseLong(properties.get(DEFAULT_PROPERTY_KEY));
150     }
151 
152     public long timeoutFor(final TimeoutType timeoutType)
153     {
154         String timeout = properties.get(propKey(timeoutType));
155         if (timeout == null)
156         {
157             return defaultValue(timeoutType);
158         }
159         try
160         {
161             return Long.parseLong(timeout);
162         }
163         catch (NumberFormatException e)
164         {
165             log.warn(new StringDescription().appendText("Corrupted property value for key ").appendValue(propKey(timeoutType))
166                     .appendText(": ").appendValue(timeout).appendText(". Returning default timeout value: ")
167                     .appendValue(defaultValue).toString());
168             return defaultValue;
169         }
170     }
171 
172     private long defaultValue(final TimeoutType timeoutType)
173     {
174         if (TimeoutType.EVALUATION_INTERVAL == timeoutType)
175         {
176             return Timeouts.DEFAULT_INTERVAL;
177         }
178         return defaultValue;
179     }
180 }