1   package com.atlassian.pageobjects;
2   
3   import java.lang.reflect.Constructor;
4   import java.lang.reflect.InvocationTargetException;
5   import java.net.InetAddress;
6   import java.net.UnknownHostException;
7   
8   /**
9    * Constructs a {@link TestedProduct}.  The {@link TestedProduct} instance is created by calling the constructor
10   * with the following method signature:
11   * <pre>
12   *   TestedProduct(TesterFactory, ProductInstance)
13   * </pre>
14   */
15  public class TestedProductFactory
16  {
17      /**
18       * A factory for {@link Tester} instances
19       * @param <T> The tester type
20       */
21      public static interface TesterFactory<T>
22      {
23          /**
24           * @return The tester instance to be used through the tested product
25           */
26          T create();
27      }
28  
29      /**
30       * A factory that always returns the same {@link Tester}
31       * @param <T> The tester type
32       */
33      public static class SingletonTesterFactory<T extends Tester> implements TesterFactory<T>
34      {
35          private final T tester;
36  
37          public SingletonTesterFactory(T tester)
38          {
39              this.tester = tester;
40          }
41  
42          public T create()
43          {
44              return tester;
45          }
46      }
47  
48      /**
49       * Creates a tested product, allowing the instance to choose its own default {@link Tester} and instance id
50       * @param testedProductClass The tested product class name
51       * @return The created tested product
52       */
53      @SuppressWarnings("unchecked")
54      public static TestedProduct<?> create(String testedProductClass)
55      {
56          Class<TestedProduct<?>> clazz;
57          try
58          {
59              clazz = (Class<TestedProduct<?>>) TestedProductFactory.class.getClassLoader().loadClass(testedProductClass);
60          }
61          catch (ClassNotFoundException e)
62          {
63              throw new IllegalArgumentException("Cannot find tested product class: " + testedProductClass);
64          }
65          return create(clazz);
66      }
67  
68      /**
69       * Creates a tested product, allowing the instance to choose its own default {@link Tester} and instance id
70       * @param testedProductClass The tested product class
71       * @param <P> The tested product type
72       * @return The created tested product
73       */
74      public static <P extends TestedProduct<?>> P create(Class<P> testedProductClass)
75      {
76          return create(testedProductClass, getDefaultInstanceId(testedProductClass), null);
77      }
78  
79      /**
80       * Creates a tested product using the passed tester factory and instance id.
81       * @param testedProductClass The tested product class
82       * @param instanceId The instance id
83       * @param testerFactory The tester factory to use to pass to the tested product
84       * @param <P> The tested product type
85       * @return The created tested product
86       */
87      public static <P extends TestedProduct<?>> P create(Class<P> testedProductClass, String instanceId, TesterFactory<?> testerFactory)
88      {
89          final String contextPath, baseUrl;
90          final int httpPort;
91  
92          final String ampsBaseUrl = System.getProperty("baseurl." + instanceId);
93          final ProductInstance instance;
94          if (ampsBaseUrl != null)    // running within an AMPS IntegrationTestMojo invocation - read sys props for env vars
95          {
96              httpPort = Integer.getInteger("http." + instanceId + ".port");
97              contextPath = System.getProperty("context." + instanceId + ".path");
98              baseUrl = ampsBaseUrl;
99          }
100         else
101         {
102             Defaults defaults = getDefaultsAnnotation(testedProductClass);
103             httpPort = defaults.httpPort();
104             contextPath = defaults.contextPath();
105             baseUrl = "http://" + getLocalHostName() + ":" + httpPort + contextPath;
106         }
107         instance = new DefaultProductInstance(instanceId, httpPort, contextPath, baseUrl);
108         return create(testedProductClass, instance, testerFactory);
109     }
110 
111     private static String getDefaultInstanceId(Class<?> testedProductClass)
112     {
113         Defaults annotation = getDefaultsAnnotation(testedProductClass);
114         return annotation.instanceId();
115     }
116 
117     private static Defaults getDefaultsAnnotation(Class<?> testedProductClass)
118     {
119         Defaults annotation = testedProductClass.getAnnotation(Defaults.class);
120         if (annotation == null)
121         {
122             throw new IllegalArgumentException("The tested product class '" + testedProductClass.getName() + "' is missing the @Defaults annotation");
123         }
124         return annotation;
125     }
126 
127     private static <P extends TestedProduct<?>> P create(Class<P> testedProductClass, ProductInstance instance, TesterFactory<?> testerFactory) {
128         try
129         {
130             Constructor<P> c = testedProductClass.getConstructor(TesterFactory.class, ProductInstance.class);
131             return c.newInstance(testerFactory, instance);
132         }
133         catch (NoSuchMethodException e)
134         {
135             throw new RuntimeException(e);
136         }
137         catch (InvocationTargetException e)
138         {
139             throw new RuntimeException(e);
140         }
141         catch (InstantiationException e)
142         {
143             throw new RuntimeException(e);
144         }
145         catch (IllegalAccessException e)
146         {
147             throw new RuntimeException(e);
148         }
149     }
150 
151     private static String getLocalHostName()
152     {
153         try
154         {
155             return InetAddress.getLocalHost().getHostName();
156         }
157         catch (UnknownHostException e)
158         {
159             throw new RuntimeException(e);
160         }
161     }
162 
163     /**
164      * Information representing the product instance being tested
165      */
166     private static class DefaultProductInstance implements ProductInstance
167     {
168         private final String baseUrl;
169         private final String instanceId;
170         private final int httpPort;
171         private final String contextPath;
172 
173 
174         public DefaultProductInstance(final String instanceId, final int httpPort, final String contextPath, final String baseUrl)
175         {
176             this.instanceId = instanceId;
177             this.httpPort = httpPort;
178             this.contextPath = contextPath;
179             this.baseUrl = baseUrl;
180         }
181 
182         public String getBaseUrl()
183         {
184             return baseUrl.toLowerCase();
185         }
186 
187         public int getHttpPort()
188         {
189             return httpPort;
190         }
191 
192         public String getContextPath()
193         {
194             return contextPath;
195         }
196 
197         public String getInstanceId() {
198             return instanceId;
199         }
200     }
201 }