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