1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package sfutils;
29
30 import java.io.IOException;
31
32 import java.net.MalformedURLException;
33
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.Map;
38 import java.util.WeakHashMap;
39
40 import com.meterware.httpunit.ClientProperties;
41 import com.meterware.httpunit.GetMethodWebRequest;
42 import com.meterware.httpunit.HttpUnitOptions;
43 import com.meterware.httpunit.WebConversation;
44 import com.meterware.httpunit.WebRequest;
45 import com.meterware.httpunit.WebResponse;
46 import com.meterware.httpunit.WebForm;
47 import com.meterware.httpunit.WebLink;
48
49 import org.xml.sax.SAXException;
50
51 public class SourceForgeClient {
52
53 /***
54 * The {@link String} that represents the URL to use to log in to <a
55 * href="http://sourceforge.net/">SourceForge</a>.
56 */
57 private static final String LOGIN_URL =
58 "http://sourceforge.net/account/login.php";
59
60 /***
61 * The leading portion of the URL required to access a <a
62 * href="http://sourceforge.net/">SourceForge</a> project.
63 */
64 private static final String PROJECT_PREFIX =
65 "http://sourceforge.net/projects/";
66
67 /***
68 * A {@link Map} of project IDs indexed by project short names. This field is
69 * thread-safe.
70 */
71 private static final Map PROJECT_ID_MAP =
72 Collections.synchronizedMap(new WeakHashMap());
73
74 /***
75 * Static initializer; initializes various class fields.
76 */
77 static {
78 HttpUnitOptions.setExceptionsThrownOnScriptError(false);
79 HttpUnitOptions.setScriptingEnabled(false);
80 }
81
82 /***
83 * The {@link WebConversation} to use to represent a session with the <a
84 * href="http://sourceforge.net/">SourceForge</a> website. This field will
85 * never be <code>null</code>.
86 */
87 private final WebConversation wc;
88
89 private boolean loggedIn;
90
91 public SourceForgeClient() {
92 super();
93 this.wc = new WebConversation();
94 final ClientProperties clientProperties = this.wc.getClientProperties();
95 if (clientProperties != null) {
96 clientProperties.setAcceptGzip(false);
97 }
98 }
99
100 public SourceForgeClient(final String user,
101 final String password)
102 throws InvalidCredentialsException, SourceForgeException {
103 this();
104 this.login(user, password);
105 }
106
107 protected WebConversation getWebConversation() {
108 return this.wc;
109 }
110
111 protected WebResponse getCurrentPage() {
112 final WebConversation wc = this.getWebConversation();
113 if (wc != null) {
114 return wc.getCurrentPage();
115 }
116 return null;
117 }
118
119 public boolean isLoggedIn() {
120 return this.loggedIn;
121 }
122
123 protected void setLoggedIn(final boolean loggedIn) {
124 this.loggedIn = loggedIn;
125 }
126
127 public boolean login(final String user,
128 final String password)
129 throws InvalidCredentialsException, SourceForgeException
130 {
131
132
133 if (this.isLoggedIn()) {
134 return false;
135 }
136
137
138
139 if (user == null ||
140 user.trim().equals("") ||
141 password == null) {
142 throw new InvalidCredentialsException(user, password);
143 }
144
145
146 final WebConversation wc = this.getWebConversation();
147 assertNotNull(wc, "wc");
148
149
150
151 WebResponse loginPageResponse = null;
152 try {
153 loginPageResponse =
154 wc.getResponse(new GetMethodWebRequest("http://sourceforge.net/account/login.php"));
155 } catch (final IOException kaboom) {
156 throw new SourceForgeException(kaboom);
157 } catch (final SAXException kaboom) {
158 throw new SourceForgeException(kaboom);
159 }
160 assertNotNull(loginPageResponse, "loginPageResponse");
161
162
163
164 WebForm[] forms = null;
165 try {
166 forms = loginPageResponse.getForms();
167 } catch (final SAXException kaboom) {
168 throw new SourceForgeException(kaboom);
169 }
170 assertArrayFull(forms, "forms");
171
172
173 final WebForm loginForm =
174 this.findFormWithAction(forms,
175 "https://sourceforge.net/account/login.php");
176 assertNotNull(loginForm, "loginForm");
177
178
179
180
181 loginForm.setParameter("form_loginname", user);
182 loginForm.setParameter("form_pw", password);
183 loginForm.removeParameter("stay_in_ssl");
184 loginForm.setParameter("persistent_login", "1");
185
186
187 final WebRequest loginSubmissionRequest = loginForm.getRequest("login");
188 assertNotNull(loginSubmissionRequest, "loginSubmissionRequest");
189 WebResponse loginResponse = null;
190 try {
191 loginResponse =
192 wc.getResponse(loginSubmissionRequest);
193 } catch (final IOException kaboom) {
194 throw new SourceForgeException(kaboom);
195 } catch (final SAXException kaboom) {
196 throw new SourceForgeException(kaboom);
197 }
198 assertNotNull(loginResponse, "loginResponse");
199
200
201 String text = null;
202 try {
203 text = loginResponse.getText();
204 } catch (final IOException kaboom) {
205 throw new SourceForgeException(kaboom);
206 }
207 if (text == null || text.indexOf("Invalid Password or User Name") >= 0) {
208 throw new InvalidCredentialsException(user, password);
209 }
210
211
212 this.setLoggedIn(true);
213 return true;
214
215 }
216
217 /***
218 * Returns the <a href="http://sourceforge.net/">SourceForge</a> project
219 * identifier given a project short name, or <code>-1</code> if the project
220 * does not exist.
221 *
222 * @param projectShortName
223 * the name of the project whose identifier should be returned;
224 * may be <code>null</code> in which case <code>-1</code> will
225 * be returned
226 * @return the identifier corresponding to the <a
227 * href="http://sourceforge.net/">SourceForge</a> project with
228 * the supplied short name, or <code>-1</code>
229 * @exception SourceForgeException
230 * if an error occurs
231 */
232 public int getProjectID(final String projectShortName)
233 throws SourceForgeException {
234 if (projectShortName == null) {
235 return -1;
236 }
237
238
239 final Integer projectIDInteger = (Integer)PROJECT_ID_MAP.get(projectShortName);
240 if (projectIDInteger != null) {
241 return projectIDInteger.intValue();
242 }
243
244
245 final WebConversation webConversation = this.getWebConversation();
246 assertNotNull(webConversation, "webConversation");
247
248
249
250 WebResponse summaryPage = null;
251 try {
252 summaryPage =
253 webConversation.getResponse(new GetMethodWebRequest(PROJECT_PREFIX +
254 projectShortName));
255 } catch (final IOException kaboom) {
256 throw new SourceForgeException(kaboom);
257 } catch (final SAXException kaboom) {
258 throw new SourceForgeException(kaboom);
259 }
260 assertNotNull(summaryPage, "summaryPage");
261
262
263
264
265
266 WebLink viewMembersLink = null;
267 try {
268 viewMembersLink = summaryPage.getLinkWith("[View Members]");
269 } catch (final SAXException kaboom) {
270 throw new SourceForgeException(kaboom);
271 }
272 if (viewMembersLink == null) {
273
274
275 String text = null;
276 try {
277 text = summaryPage.getText();
278 } catch (final IOException kaboom) {
279 throw new SourceForgeException(kaboom);
280 }
281 assertNotNull(text, "text");
282 if (text.indexOf("Invalid Project") >= 0) {
283 throw new NoSuchProjectException(projectShortName);
284 }
285
286 throw new SourceForgeUIChangeException();
287 }
288
289
290
291
292
293 final String linkText = viewMembersLink.getURLString();
294 assertNotNull(linkText, "linkText");
295 final int groupIDIndex = linkText.indexOf("?group_id=");
296 if (groupIDIndex < 0) {
297 throw new SourceForgeUIChangeException();
298 }
299 int projectID = -1;
300 final String projectIDString =
301 linkText.substring(groupIDIndex + "?group_id=".length());
302
303
304
305 if (projectIDString != null) {
306 try {
307 projectID = Integer.parseInt(projectIDString);
308 } catch (final NumberFormatException kaboom) {
309 projectID = -1;
310 }
311 }
312 PROJECT_ID_MAP.put(projectShortName, new Integer(projectID));
313 return projectID;
314 }
315
316 /***
317 * Extracts a {@link WebForm} from the supplied array of {@link WebForm}s,
318 * provided that its {@linkplain WebForm#getAction() action} is equal to the
319 * supplied {@link String}. This method may return <code>null</code>.
320 *
321 * @param forms
322 * the array of {@link WebForm}s to consider; if
323 * <code>null</code> then <code>null</code> will be returned
324 * @param action
325 * the action {@link String} to which a given {@link WebForm}'s
326 * {@linkplain WebForm#getAction() action} must be equal in
327 * order for that {@link WebForm} to be returned; if
328 * <code>null</code> then <code>null</code> will be returned
329 * @return a {@link WebForm} from the supplied array of {@link WebForm}s,
330 * provided that its {@linkplain WebForm#getAction() action} is
331 * equal to the supplied {@link String}, or <code>null</code>
332 */
333 protected WebForm findFormWithAction(final WebForm[] forms,
334 final String action) {
335 if (forms == null || forms.length <= 0) {
336 return null;
337 }
338 WebForm form;
339 String formAction;
340 for (int i = 0; i < forms.length; i++) {
341 form = forms[i];
342 if (form != null) {
343 formAction = form.getAction();
344 if (formAction == null) {
345 if (action == null) {
346 return null;
347 }
348 } else {
349 if (formAction.equals(action)) {
350 return form;
351 }
352 }
353 }
354 }
355 return null;
356 }
357
358 /***
359 * Returns an array of {@link WebForm}s whose elements are those {@link
360 * WebForm}s extracted from the supplied {@link WebForm} array with
361 * {@linkplain WebForm#getAction() actions} equal to the supplied {@link
362 * String}. This method never returns <code>null</code>.
363 *
364 * @param forms
365 * the {@link WebForm} array in which to find candidates; may
366 * be <code>null</code>
367 * @param action
368 * the {@linkplain WebForm#getAction() action} {@link String} to
369 * match; may be <code>null</code>
370 * @return an array of {@link WebForm}s whose elements are those {@link
371 * WebForm}s extracted from the supplied {@link WebForm} array
372 * with {@linkplain WebForm#getAction() actions} equal to the
373 * supplied {@link String}; never <code>null</code>
374 */
375 protected WebForm[] retainFormsWithAction(final WebForm[] forms,
376 final String action) {
377 if (forms == null || forms.length <= 0) {
378 return new WebForm[0];
379 }
380 final Collection returnForms = new ArrayList(forms.length);
381 WebForm form;
382 String formAction;
383 for (int i = 0; i < forms.length; i++) {
384 form = forms[i];
385 if (form != null) {
386 formAction = form.getAction();
387 if (formAction == null) {
388 if (action == null) {
389 returnForms.add(form);
390 }
391 } else {
392 if (formAction.equals(action)) {
393 returnForms.add(form);
394 }
395 }
396 }
397 }
398 return (WebForm[])returnForms.toArray(new WebForm[returnForms.size()]);
399 }
400
401 protected WebForm[] retainFormsWithParameterNamed(final WebForm[] forms,
402 final String parameter) {
403 if (forms == null || forms.length <= 0) {
404 return new WebForm[0];
405 }
406 final Collection returnForms = new ArrayList(forms.length);
407 WebForm form;
408 for (int i = 0; i < forms.length; i++) {
409 form = forms[i];
410 if (form != null && form.hasParameterNamed(parameter)) {
411 returnForms.add(form);
412 }
413 }
414 return (WebForm[])returnForms.toArray(new WebForm[returnForms.size()]);
415 }
416
417 protected WebForm[] retainFormsWithParameterValue(final WebForm[] forms,
418 final String parameter,
419 final String value) {
420 if (forms == null || forms.length <= 0) {
421 return new WebForm[0];
422 }
423 final Collection returnForms = new ArrayList(forms.length);
424 WebForm form;
425 String parameterValue;
426 for (int i = 0; i < forms.length; i++) {
427 form = forms[i];
428 if (form != null) {
429 parameterValue = form.getParameterValue(parameter);
430 if (parameterValue != null && parameterValue.equals(value)) {
431 returnForms.add(form);
432 }
433 }
434 }
435 return (WebForm[])returnForms.toArray(new WebForm[returnForms.size()]);
436 }
437
438 /***
439 * A convenience method that throws a {@link SourceForgeException} if the
440 * supplied {@link Object} is <code>null</code>. This method is primarily
441 * used to check method arguments and in other cases where a checked {@link
442 * Exception} is preferred to the usual {@link IllegalArgumentException}
443 * alternative.
444 *
445 * @param object
446 * the {@link Object} to check; if <code>null</code> a {@link
447 * SourceForgeException} will be thrown
448 * @param message
449 * a message describing the problem if the supplied {@link
450 * Object} is <code>null</code>; may be <code>null</code>
451 * @exception SourceForgeException
452 * if the supplied {@link Object} is <code>null</code>
453 */
454 protected static final void assertNotNull(final Object object,
455 final String message)
456 throws SourceForgeException {
457 if (object == null) {
458 throw new SourceForgeException(message);
459 }
460 }
461
462 /***
463 * A convenience metohd that throws either a {@link SourceForgeException} or an
464 * {@link SourceForgeException} if the supplied {@link Object} array is
465 * <code>null</code> or has a length less than or equal to <code>0</code>
466 * respectively.
467 *
468 * @param object
469 * the {@link Object} array to check; if <code>null</code> or
470 * empty a {@link SourceForgeException} will be thrown
471 * @param message
472 * a message describing the problem if the supplied {@link
473 * Object} array is <code>null</code> or empty; may be
474 * <code>null</code>
475 * @exception SourceForgeException
476 * if the supplied {@link Object} array is <code>null</code> or
477 * empty
478 */
479 protected static final void assertArrayFull(final Object[] object,
480 final String message)
481 throws SourceForgeException, SourceForgeException {
482 assertNotNull(object, message);
483 if (object.length <= 0) {
484 throw new SourceForgeException(message);
485 }
486 }
487
488 }