Line | Hits | Source |
---|---|---|
1 | /* | |
2 | * Copyright (C) 2006 TopCoder Inc., All Rights Reserved. | |
3 | */ | |
4 | package com.topcoder.testframework.web; | |
5 | ||
6 | import com.topcoder.testframework.AntTaskInfo; | |
7 | import com.topcoder.testframework.ApplicationServer; | |
8 | import org.apache.cactus.integration.ant.container.Container; | |
9 | import org.apache.cactus.integration.ant.container.ContainerFactory; | |
10 | import org.apache.cactus.integration.ant.container.ContainerRunner; | |
11 | import org.apache.cactus.integration.ant.deployment.DeployableFile; | |
12 | import org.apache.cactus.integration.ant.deployment.EarParser; | |
13 | import org.apache.cactus.integration.ant.deployment.WarParser; | |
14 | import org.apache.cactus.integration.ant.util.DefaultAntTaskFactory; | |
15 | import org.apache.tools.ant.BuildException; | |
16 | ||
17 | import java.io.File; | |
18 | import java.lang.reflect.Method; | |
19 | import java.net.MalformedURLException; | |
20 | import java.net.URL; | |
21 | ||
22 | ||
23 | /** | |
24 | * This class is derived from {@link ApplicationServer} class. It represents web application server to be used while | |
25 | * running tests. It uses Cactus as a back-end. It tries to support all the servers supported by Cactus. Name of | |
26 | * corresponding XML element in the Ant build file should be equal to the Cactus container type representing the desired | |
27 | * server. | |
28 | * <p/> | |
29 | * <b>Supported attributes:</b> <ul><li><tt>timeout</tt> - non-required, specifies the timeout to wait for successful | |
30 | * server startup in milliseconds, default value is 180000 milliseconds</li> <li><tt>testurl</tt> - non-required, | |
31 | * specifies a url to be used to test whether the server has started up and is available, if not specified will be | |
32 | * calculated from the prepared deployable (war or ear) file and server startup parameters</li></ul> | |
33 | * <p/> | |
34 | * This class is mutable, i.e. not thread-safe. | |
35 | * | |
36 | * @author real_vg, TCSDEVELOPER | |
37 | * @version 1.0 | |
38 | */ | |
39 | public class DefaultWebApplicationServer extends ApplicationServer { | |
40 | /** | |
41 | * This is the default timeout used when no timeout has been specified. It is 180000 ms, i.e. 3 minutes. | |
42 | */ | |
43 | private static final int DEFAULT_TIMEOUT = 180000; | |
44 | ||
45 | /** | |
46 | * This field represents the Cactus container representing the server to be used when running the tests. Is | |
47 | * initialized in constructor, using {@link org.apache.cactus.integration.ant.container.ContainerFactory} to create | |
48 | * an instance of the class. | |
49 | */ | |
50 | private Container container; | |
51 | ||
52 | /** | |
53 | * This field stores instance of Cactus ContainerRunner class to be used for starting/stopping the server | |
54 | * represented by the container field. | |
55 | */ | |
56 | private ContainerRunner runner; | |
57 | ||
58 | /** | |
59 | * This field represents the <tt>timeout</tt> attribute, which specifies the amount of time to wait for server to be | |
60 | * started (in milliseconds). The field is initialized to <tt>180000</tt> and set via the {@link #setTimeout(long)} | |
61 | * method. | |
62 | */ | |
63 | 68 | private long timeout = DEFAULT_TIMEOUT; |
64 | ||
65 | /** | |
66 | * This field represents the <tt>testurl</tt> attribute, which specifies the URL used to check whether the server is | |
67 | * running. | |
68 | * <p/> | |
69 | * This attribute is required only when neither <tt>warfile</tt> nor <tt>earfile</tt> attributes are specified. If | |
70 | * this attribute is set and the server is detected to be running, the server will not be started by the framework. | |
71 | * The field is initialized to <tt>null</tt> and set via the {@link #setTestURL(java.net.URL)} method. | |
72 | */ | |
73 | 68 | private URL testURL = null; |
74 | ||
75 | /** | |
76 | * Creates a DefaultWebApplicationServer instance. The type of server represented by the class is specified. Type is | |
77 | * a string like <tt>"tomcat5x"</tt>, which actually specifies the name and version of the server. | |
78 | * | |
79 | * @param type the type of the server | |
80 | * | |
81 | * @throws BuildException if the type of the server is unrecognized, or any other error happens | |
82 | * @throws IllegalArgumentException if type is <tt>null</tt> | |
83 | */ | |
84 | public DefaultWebApplicationServer(final String type) { | |
85 | //arg checking done in super method | |
86 | 69 | super(type); |
87 | 68 | final ContainerFactory containerFactory = new ContainerFactory(); |
88 | 68 | container = containerFactory.createContainer(type); |
89 | 68 | runner = new ContainerRunner(container); |
90 | 68 | } |
91 | ||
92 | /** | |
93 | * This method starts the server. In the case when <tt>startserver</tt> attribute is set to <tt>false</tt>, the | |
94 | * server should not be started, but some initialization may still be needed. | |
95 | * <p/> | |
96 | * In case there is a valid <tt>testurl</tt> attribute set on this instance, this will be used to test if the server | |
97 | * is already running. If it is running , then it is not started and stopped by the framework. | |
98 | * | |
99 | * @throws BuildException if any error happens | |
100 | */ | |
101 | public void startUp() { | |
102 | 5 | final File configuredWarFile = getWarFile(); |
103 | 5 | final File configuredEarFile = getEarFile(); |
104 | ||
105 | 5 | if ((configuredWarFile != null) && (configuredEarFile != null)) { |
106 | 1 | throw new BuildException("You must specify either the [warfile] or " |
107 | + "the [earfile] attribute but not both"); | |
108 | } | |
109 | ||
110 | // Get DeployableFile instance | |
111 | 4 | DeployableFile deployableFile = null; |
112 | 4 | if (configuredWarFile != null) { |
113 | 2 | deployableFile = WarParser.parse(configuredWarFile); |
114 | 2 | } else if (configuredEarFile != null) { |
115 | 0 | deployableFile = EarParser.parse(configuredEarFile); |
116 | } | |
117 | ||
118 | // Retrieve Ant task information | |
119 | 4 | final AntTaskInfo taskInfo = getAntTaskInfo(); |
120 | // Create and set ant task factory | |
121 | 4 | container.setAntTaskFactory(new DefaultAntTaskFactory(taskInfo.getProject(), taskInfo.getTaskName(), |
122 | taskInfo.getLocation(), taskInfo.getTarget())); | |
123 | ||
124 | // Set deployable file | |
125 | 4 | container.setDeployableFile(deployableFile); |
126 | ||
127 | 6 | callSetterByReflection(container, "setServer", String.class, getServer()); |
128 | 4 | if (getPort() > 0) { |
129 | 3 | callSetterByReflection(container, "setPort", int.class, new Integer(getPort())); |
130 | } | |
131 | 4 | callSetterByReflection(container, "setDir", File.class, getDir()); |
132 | ||
133 | // Init the container | |
134 | 4 | container.init(); |
135 | // Set URL to test connection with | |
136 | 3 | if (getTestURL() != null) { |
137 | 0 | runner.setURL(getTestURL()); |
138 | // according to https://software.topcoder.com/forum/c_forum_message.jsp?f=20015788&r=21551343 | |
139 | // we set the context url to be the testURL | |
140 | 0 | System.setProperty("cactus.contextURL", getTestURL().toExternalForm()); |
141 | 3 | } else if (deployableFile != null) { |
142 | 2 | final String spec = container.getBaseURL() + "/" |
143 | + deployableFile.getTestContext() | |
144 | + deployableFile.getServletRedirectorMapping() | |
145 | + "?Cactus_Service=RUN_TEST"; | |
146 | try { | |
147 | 2 | runner.setURL(new URL(spec)); |
148 | 0 | } catch (MalformedURLException e) { |
149 | 0 | throw new BuildException("The URL spec [" + spec + "] could not be converted into a valid URL.", e); |
150 | 2 | } |
151 | // sets the URL to be used by the test case client when | |
152 | // connecting to the container to call the container-side test cases | |
153 | // see https://software.topcoder.com/forum/c_forum_message.jsp?f=20015788&r=21553769 | |
154 | 2 | System.setProperty("cactus.contextURL", container.getBaseURL() + "/" + deployableFile.getTestContext()); |
155 | } else { | |
156 | 1 | throw new BuildException("Unable to start the container: Neither a valid 'testUrl' attribute nor " |
157 | + "a deployable file ('warFile' or 'earFile') were specified."); | |
158 | } | |
159 | ||
160 | // Set timeout | |
161 | 2 | runner.setTimeout(getTimeout()); |
162 | ||
163 | // Start the container (starts server if server wasn't already running) | |
164 | 2 | runner.startUpContainer(); |
165 | 2 | } |
166 | ||
167 | /** | |
168 | * This method stops the server, and possibly clean-ups the data. The server should not be stopped if it wasn't | |
169 | * started by the {@link #startUp()} method. | |
170 | * | |
171 | * @throws BuildException if any error happens | |
172 | */ | |
173 | public void shutDown() { | |
174 | 23 | runner.shutDownContainer(); |
175 | 3 | } |
176 | ||
177 | /** | |
178 | * This method initializes the server with data. In the case when <tt>initserver</tt> attribute is set to | |
179 | * <tt>false</tt>, does nothing, or can check if the data are properly initialized, the implementation can choose | |
180 | * the preferred way. Subclasses must implement this method to perform the actual task of initializing server with | |
181 | * data. | |
182 | * | |
183 | * @throws BuildException if any error happens | |
184 | */ | |
185 | public void initData() { | |
186 | // This method is intentionally empty | |
187 | 1 | } |
188 | ||
189 | /** | |
190 | * This method cleans up the data initialized by the {@link #initData()} method. In the case when | |
191 | * <tt>initserver</tt> attribute is set to <tt>false</tt> or data was not initialized, does nothing. Subclasses must | |
192 | * implement this method to perform the actual task of cleaning-up the data. | |
193 | * | |
194 | * @throws BuildException if any error happens | |
195 | */ | |
196 | public void cleanUpData() { | |
197 | // This method is intentionally empty | |
198 | 1 | } |
199 | ||
200 | /** | |
201 | * This method sets the value of the <tt>timeout</tt> attribute, which specifies the amount of time to wait for | |
202 | * server to be started (in milliseconds). | |
203 | * | |
204 | * @param timeout the value of the <tt>timeout</tt> attribute to be set, in milliseconds | |
205 | * | |
206 | * @throws IllegalArgumentException if timeout is negative | |
207 | */ | |
208 | public void setTimeout(final long timeout) { | |
209 | 2 | if (timeout < 0) { |
210 | 1 | throw new IllegalArgumentException( |
211 | "The parameter named [timeout] was expected to be >=0 , but was [" + timeout + "]."); | |
212 | } | |
213 | ||
214 | 1 | this.timeout = timeout; |
215 | 1 | } |
216 | ||
217 | /** | |
218 | * This method returns the value of the <tt>timeout</tt> attribute, which specifies the amount of time to wait for | |
219 | * server to be started (in milliseconds). | |
220 | * | |
221 | * @return the value of the <tt>timeout</tt> attribute, in milliseconds | |
222 | */ | |
223 | public long getTimeout() { | |
224 | 4 | return timeout; |
225 | } | |
226 | ||
227 | /** | |
228 | * This method sets the value of the <tt>testurl</tt> attribute, which specifies the URL used to check whether the | |
229 | * server is running. If this attribute is set and the server is running, the server will not be started by the | |
230 | * framework. | |
231 | * | |
232 | * @param testURL the value of the <tt>testurl</tt> attribute to be set | |
233 | */ | |
234 | public void setTestURL(final URL testURL) { | |
235 | 1 | this.testURL = testURL; |
236 | 1 | } |
237 | ||
238 | /** | |
239 | * This method returns the value of the <tt>testurl</tt> attribute, which specifies URL used to check if the server | |
240 | * is running. If this attribute is set and the server is running, the server will not be started by the framework. | |
241 | * | |
242 | * @return the value of the <tt>testurl</tt> attribute | |
243 | */ | |
244 | public URL getTestURL() { | |
245 | 5 | return testURL; |
246 | } | |
247 | ||
248 | /** | |
249 | * This method is a utility for the reflective invocation of a setter method in a Container instance. It tries to | |
250 | * find a method with the given name and argument type in the given instance and in case found, invokes that method | |
251 | * using the given value as argument. | |
252 | * | |
253 | * @param instance the instance on which the setter method shall be called | |
254 | * @param setterName the name of the setter method | |
255 | * @param valueType the type of the argument in the setter method declaration | |
256 | * @param value the value to be passed when invoking the setter method | |
257 | * | |
258 | * @throws IllegalArgumentException in case the given instance, setterName or valueType are <tt>null</tt>, or the | |
259 | * given setterName is an empty(trim'd) String | |
260 | * @throws BuildException in case the appropriate setter method was not found or threw an Throwable during | |
261 | * invocation | |
262 | */ | |
263 | private static void callSetterByReflection(final Object instance, final String setterName, final Class valueType, | |
264 | final Object value) { | |
265 | 11 | if (instance == null) { |
266 | 0 | throw new IllegalArgumentException("The parameter named [instance] was null."); |
267 | } | |
268 | 11 | if (setterName == null) { |
269 | 0 | throw new IllegalArgumentException("The parameter named [setterName] was null."); |
270 | } | |
271 | 11 | if (setterName.trim().length() == 0) { |
272 | 0 | throw new IllegalArgumentException("The parameter named [setterName] was an empty String."); |
273 | } | |
274 | 11 | if (valueType == null) { |
275 | 0 | throw new IllegalArgumentException("The parameter named [valueType] was null."); |
276 | } | |
277 | ||
278 | 11 | final Class instanceType = instance.getClass(); |
279 | ||
280 | final Method method; | |
281 | try { | |
282 | 11 | method = instanceType.getMethod(setterName, new Class[]{valueType}); |
283 | 0 | } catch (NoSuchMethodException e) { |
284 | 0 | throw new BuildException("The container [" + instance + "] of type [" + instanceType.getName() |
285 | + "] did not have an appropriate setter method named [" + setterName + "] with argument of type [" | |
286 | + valueType.getName() + "].", e); | |
287 | 11 | } |
288 | try { | |
289 | 11 | method.invoke(instance, new Object[]{value}); |
290 | 0 | } catch (Throwable e) { |
291 | // catch of Throwable is normally discouraged by the TC coding style, but | |
292 | // as a lot of Errors can arise from a reflection invocation and these | |
293 | // errors don't need to be let pass through this catch clause (as they | |
294 | // definitely belong to the reflective invocation and not the normal program | |
295 | // execution) all Throwables are caught here and | |
296 | // wrapped into a BuildException. | |
297 | 0 | throw new BuildException("The container [" + instance + "] of type [" + instanceType.getName() |
298 | + "] did throw an exception upon invocation of the setter method named [" + setterName | |
299 | + "] with argument of type [" + valueType.getName() + "], using the argument [" + value | |
300 | + "] in the invocation.", e); | |
301 | 11 | } |
302 | 11 | } |
303 | } |
this report was generated by version 1.0.5 of jcoverage. |
copyright © 2003, jcoverage ltd. all rights reserved. |