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.frs;
29
30 import java.io.BufferedInputStream;
31 import java.io.BufferedOutputStream;
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.OutputStream;
37 import java.io.UnsupportedEncodingException;
38
39 import java.net.URL;
40 import java.net.URLConnection;
41 import java.net.URLEncoder;
42
43 import java.text.DateFormat;
44 import java.text.SimpleDateFormat;
45
46 import java.util.Arrays;
47 import java.util.Collections;
48 import java.util.Comparator;
49 import java.util.Date;
50 import java.util.Iterator;
51 import java.util.List;
52 import java.util.SortedSet;
53 import java.util.TreeSet;
54
55 import java.util.logging.Logger;
56
57 import java.util.regex.Matcher;
58 import java.util.regex.Pattern;
59
60 import com.meterware.httpunit.GetMethodWebRequest;
61 import com.meterware.httpunit.TableCell;
62 import com.meterware.httpunit.UploadFileSpec;
63 import com.meterware.httpunit.WebConversation;
64 import com.meterware.httpunit.WebForm;
65 import com.meterware.httpunit.WebLink;
66 import com.meterware.httpunit.WebResponse;
67 import com.meterware.httpunit.WebTable;
68
69 import org.xml.sax.SAXException;
70
71 import sfutils.Administrator;
72 import sfutils.InvalidCredentialsException;
73 import sfutils.InvalidProjectIDException;
74 import sfutils.NotLoggedInException;
75 import sfutils.PermissionDeniedException;
76 import sfutils.Project;
77 import sfutils.SourceForgeClient;
78 import sfutils.SourceForgeException;
79 import sfutils.SourceForgeUIChangeException;
80
81 public class FileReleaseSystem extends SourceForgeClient implements Publisher {
82
83
84
85
86
87 private static final Logger LOGGER;
88
89 private static final DateFormat DATE_FORMATTER;
90
91 private static final Comparator REVERSE_COMPARATOR;
92
93 private static final Object PATTERN_LOCK;
94
95 private static final Pattern RELEASE_ID_PATTERN;
96
97 static {
98 LOGGER = Logger.getLogger(FileReleaseSystem.class.getName());
99 assert LOGGER != null;
100 DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
101 REVERSE_COMPARATOR = new ReverseComparator();
102 PATTERN_LOCK = new byte[0];
103 RELEASE_ID_PATTERN = Pattern.compile("release_id=(//d+)");
104 assert RELEASE_ID_PATTERN != null;
105 }
106
107 private volatile int uploadThreadsOutstanding;
108
109 private Exception uploadException;
110
111 private FileUploadListener[] listeners;
112
113 public FileReleaseSystem() {
114 super();
115 }
116
117 public FileReleaseSystem(final String user,
118 final String password)
119 throws InvalidCredentialsException, SourceForgeException {
120 super(user, password);
121 }
122
123
124
125
126
127 /***
128 * Publishes the supplied {@link FileRelease} to its associated {@link
129 * Project} area on <a href="http://sourceforge.net/">SourceForge</a>.
130 *
131 * @param release
132 * the {@link FileRelease} to publish; must not be
133 * <code>null</code>
134 * @exception SourceForgeException
135 * if the supplied {@link FileRelease} could not be published
136 */
137 public void publish(final FileRelease release)
138 throws InvalidProjectIDException,
139 InvalidPackageIDException,
140 InvalidFileReleaseIDException,
141 TooManyPackagesException,
142 TooManyFileReleasesException,
143 PermissionDeniedException,
144 NotLoggedInException,
145 SourceForgeUIChangeException,
146 SourceForgeException {
147 assertNotNull(release, "release");
148 if (!this.isLoggedIn()) {
149 this.login(release);
150 }
151
152
153 final File[] files = release.getFiles();
154 final boolean hasFiles = files != null && files.length > 0;
155 FileUploadListener notifier = null;
156 try {
157 if (hasFiles) {
158 notifier =
159 new FileUploadListener() {
160 public void fileUploaded(final FileUploadEvent event) {
161 synchronized (FileReleaseSystem.this) {
162 --uploadThreadsOutstanding;
163 FileReleaseSystem.this.notify();
164 }
165 }
166
167 public void exceptionCaught(final FileUploadEvent event) {
168 synchronized (FileReleaseSystem.this) {
169 if (event != null && uploadException == null) {
170 uploadException = event.getException();
171 }
172 --uploadThreadsOutstanding;
173 FileReleaseSystem.this.notify();
174 }
175 }
176 };
177 this.addFileUploadListener(notifier);
178 File file;
179 for (int i = 0; i < files.length; i++) {
180 file = files[i];
181 if (file != null) {
182 synchronized (this) {
183 ++this.uploadThreadsOutstanding;
184 this.uploadFileAsynchronously(file);
185 }
186 }
187 }
188 }
189
190
191
192
193 this.editExistingFileRelease(release);
194 if (hasFiles) {
195
196
197
198
199
200
201 synchronized (this) {
202 while (this.uploadThreadsOutstanding > 0) {
203 this.ensureNoUploadErrors();
204 try {
205 this.wait(5 * 60 * 1000);
206 } catch (final InterruptedException throwMe) {
207 throw new SourceForgeException(throwMe);
208 }
209 }
210 this.ensureNoUploadErrors();
211 }
212
213
214
215 this.addFiles(release);
216 }
217 if (release.getNotifyOthers()) {
218 this.notifyOthers(release);
219 }
220 } finally {
221 if (notifier != null) {
222 this.removeFileUploadListener(notifier);
223 }
224 }
225 }
226
227 public void editExistingFileRelease(final FileRelease release)
228 throws InvalidCredentialsException,
229 InvalidProjectIDException,
230 InvalidPackageIDException,
231 InvalidPackageNameException,
232 InvalidFileReleaseIDException,
233 TooManyPackagesException,
234 TooManyFileReleasesException,
235 NotLoggedInException,
236 PermissionDeniedException,
237 SourceForgeUIChangeException,
238 SourceForgeException {
239 assertNotNull(release, "release");
240 this.editExistingFileRelease(this.getProjectID(release),
241 this.getPackageID(release),
242 this.getFileReleaseID(release),
243 release.getReleaseDate(),
244 release.isHidden(),
245 release.getPreserveFormattedText(),
246 release.getChangeLogFile(),
247 release.getChangeLog(),
248 release.getReleaseNotesFile(),
249 release.getReleaseNotes());
250 }
251
252 public void editExistingFileRelease(final int projectID,
253 final int packageID,
254 final int releaseID,
255 final Date releaseDate,
256 final boolean hidden,
257 final boolean preserveFormattedText,
258 final File changeLogFile,
259 final String changeLogText,
260 final File releaseNotesFile,
261 final String releaseNotesText)
262 throws InvalidProjectIDException,
263 InvalidPackageIDException,
264 InvalidFileReleaseIDException,
265 NotLoggedInException,
266 SourceForgeUIChangeException,
267 SourceForgeException {
268 final WebForm editExistingReleaseForm =
269 this.getEditExistingReleaseForm(projectID, packageID, releaseID);
270 if (editExistingReleaseForm == null) {
271 throw new SourceForgeUIChangeException();
272 }
273 if (releaseDate != null) {
274 final String formattedDate;
275 synchronized (DATE_FORMATTER) {
276 formattedDate = DATE_FORMATTER.format(releaseDate);
277 }
278 if (formattedDate != null) {
279 editExistingReleaseForm.setParameter("release_date", formattedDate);
280 }
281 }
282 editExistingReleaseForm.setParameter("status_id", hidden ? "3" : "1");
283 if (preserveFormattedText) {
284 editExistingReleaseForm.setParameter("preformatted", "1");
285 } else {
286 editExistingReleaseForm.removeParameter("preformatted");
287 }
288 if (changeLogFile != null && changeLogFile.canRead()) {
289 editExistingReleaseForm.setParameter("uploaded_changes",
290 new UploadFileSpec[] {
291 new UploadFileSpec(changeLogFile)
292 });
293 } else if (changeLogText != null) {
294 editExistingReleaseForm.setParameter("release_changes", changeLogText);
295 }
296 if (releaseNotesFile != null && releaseNotesFile.canRead()) {
297 editExistingReleaseForm.setParameter("uploaded_notes",
298 new UploadFileSpec[] {
299 new UploadFileSpec(releaseNotesFile)
300 });
301 } else if (releaseNotesText != null) {
302 editExistingReleaseForm.setParameter("release_notes", releaseNotesText);
303 }
304 try {
305 editExistingReleaseForm.submit();
306 } catch (final SAXException kaboom) {
307 throw new SourceForgeException(kaboom);
308 } catch (final IOException kaboom) {
309 throw new SourceForgeException(kaboom);
310 }
311 }
312
313 public void addFiles(final FileRelease release)
314 throws NotLoggedInException,
315 InvalidPackageNameException,
316 SourceForgeUIChangeException,
317 SourceForgeException {
318 this.addFiles(this.getProjectID(release),
319 this.getPackageID(release),
320 this.getFileReleaseID(release),
321 extractShortFileNames(release));
322 }
323
324 public void addFiles(final int projectID,
325 final int packageID,
326 final int releaseID,
327 final String[] shortFileNames)
328 throws NotLoggedInException,
329 SourceForgeUIChangeException,
330 SourceForgeException {
331 this.addFiles(projectID,
332 packageID,
333 releaseID,
334 new TreeSet(Arrays.asList(shortFileNames)));
335 }
336
337 /***
338 * Adds files that have been previously uploaded to the SourceForge file
339 * release identified by the supplied release identifier. Note that this
340 * method does not <i>configure</i> the files so added. See the {@link
341 * #editFiles(FileRelease)} method for details.
342 */
343 public void addFiles(final int projectID,
344 final int packageID,
345 final int releaseID,
346 final SortedSet shortFileNamesSet)
347 throws NotLoggedInException,
348 SourceForgeUIChangeException,
349 SourceForgeException {
350 if (shortFileNamesSet == null ||
351 shortFileNamesSet.isEmpty()) {
352 return;
353 }
354 final WebForm addFilesForm =
355 this.getAddFilesForm(projectID, packageID, releaseID);
356 if (addFilesForm == null) {
357 throw new SourceForgeUIChangeException();
358 }
359 addFilesForm.setParameter("file_list[]",
360 (String[])shortFileNamesSet.toArray(new String[shortFileNamesSet.size()]));
361 try {
362 addFilesForm.submit();
363 } catch (final IOException kaboom) {
364 throw new SourceForgeException(kaboom);
365 } catch (final SAXException kaboom) {
366 throw new SourceForgeException(kaboom);
367 }
368 }
369
370 public void editFiles(final FileRelease release)
371 throws NotLoggedInException,
372 InvalidPackageNameException,
373 SourceForgeUIChangeException,
374 SourceForgeException {
375 this.editFiles(this.getProjectID(release),
376 this.getPackageID(release),
377 this.getFileReleaseID(release),
378 release);
379 }
380
381 public void editFiles(final int projectID,
382 final int packageID,
383 final int releaseID,
384 final FileSpecificationMap map)
385 throws NotLoggedInException,
386 InvalidProjectIDException,
387 InvalidPackageIDException,
388 InvalidFileReleaseIDException,
389 PermissionDeniedException,
390 SourceForgeUIChangeException,
391 SourceForgeException {
392 if (map == null) {
393 return;
394 }
395 WebResponse editReleasePage =
396 this.getEditReleasePage(projectID, packageID, releaseID);
397 if (editReleasePage == null) {
398 throw new SourceForgeUIChangeException();
399 }
400 WebForm[] forms = null;
401 try {
402 forms =
403 this.retainFormsWithAction(editReleasePage.getForms(),
404 "/project/admin/editreleases.php");
405 } catch (final SAXException kaboom) {
406 throw new SourceForgeException(kaboom);
407 }
408 if (forms == null || forms.length < 2) {
409 throw new SourceForgeUIChangeException();
410 } else if (forms.length == 2) {
411
412 return;
413 }
414
415
416
417
418 WebTable table = null;
419 try {
420 table = editReleasePage.getTableStartingWithPrefix("Filename");
421 } catch (final SAXException kaboom) {
422 throw new SourceForgeException(kaboom);
423 }
424 if (table == null) {
425 throw new SourceForgeUIChangeException();
426 }
427 WebForm form;
428 String title;
429 FileSpecification spec;
430 int formIndex = 2;
431 int tableRowIndex = 1;
432 while (formIndex < forms.length) {
433 form = forms[formIndex];
434 assert form != null;
435 if (form.hasParameterNamed("im_sure")) {
436 tableRowIndex += 3;
437 } else if (form.hasParameterNamed("processor_id") &&
438 form.hasParameterNamed("type_id")) {
439 title = table.getCellAsText(tableRowIndex, 0);
440 assert title != null;
441 spec = map.getFileSpecification(title);
442 assertNotNull(spec, "spec");
443 form.setParameter("processor_id",
444 String.valueOf(spec.getProcessorType()));
445 form.setParameter("type_id", Integer.toString(spec.getFileType()));
446 try {
447 editReleasePage = form.submit();
448 } catch (final IOException kaboom) {
449 throw new SourceForgeException(kaboom);
450 } catch (final SAXException kaboom) {
451 throw new SourceForgeException(kaboom);
452 }
453 assert editReleasePage != null;
454 if (formIndex + 1 < forms.length) {
455 try {
456 forms =
457 this.retainFormsWithAction(editReleasePage.getForms(),
458 "/project/admin/editreleases.php");
459 } catch (final SAXException kaboom) {
460 throw new SourceForgeException(kaboom);
461 }
462 if (forms == null || forms.length < 2) {
463 throw new SourceForgeUIChangeException();
464 } else if (forms.length == 2) {
465
466 return;
467 }
468 try {
469 table = editReleasePage.getTableStartingWithPrefix("Filename");
470 } catch (final SAXException kaboom) {
471 throw new SourceForgeException(kaboom);
472 }
473 if (table == null) {
474 throw new SourceForgeUIChangeException();
475 }
476 }
477 }
478 formIndex++;
479 }
480 }
481
482 public void notifyOthers(final FileRelease release)
483 throws NotLoggedInException,
484 InvalidPackageNameException,
485 SourceForgeUIChangeException,
486 SourceForgeException {
487 this.notifyOthers(this.getProjectID(release),
488 this.getPackageID(release),
489 this.getFileReleaseID(release));
490 }
491
492 public void notifyOthers(final int projectID,
493 final int packageID,
494 final int releaseID)
495 throws SourceForgeUIChangeException,
496 SourceForgeException {
497 final WebForm form =
498 this.getNotifyOthersForm(projectID, packageID, releaseID);
499 if (form == null) {
500 throw new SourceForgeUIChangeException();
501 }
502 try {
503 form.submit();
504 } catch (final SAXException kaboom) {
505 throw new SourceForgeException(kaboom);
506 } catch (final IOException kaboom) {
507 throw new SourceForgeException(kaboom);
508 }
509 }
510
511
512
513
514 public synchronized void addFileUploadListener(final FileUploadListener listener) {
515 if (listener != null) {
516 if (this.listeners == null ||
517 this.listeners.length <= 0) {
518 this.listeners = new FileUploadListener[] { listener };
519 } else {
520 final List list = Arrays.asList(this.listeners);
521 assert list != null;
522 if (!list.contains(listener)) {
523 list.add(listener);
524 }
525 this.listeners =
526 (FileUploadListener[])list.toArray(new FileUploadListener[list.size()]);
527 }
528 }
529 }
530
531 public synchronized void removeFileUploadListener(final FileUploadListener listener) {
532 if (listener != null &&
533 this.listeners != null &&
534 this.listeners.length > 0) {
535 final List list = Arrays.asList(this.listeners);
536 list.remove(listener);
537 this.listeners =
538 (FileUploadListener[])list.toArray(new FileUploadListener[list.size()]);
539 }
540 }
541
542 protected final void fireFileUploadedEvent(final File file) {
543 final FileUploadListener[] listeners;
544 synchronized (FileReleaseSystem.class) {
545 if (this.listeners == null) {
546 listeners = null;
547 } else {
548 listeners = (FileUploadListener[])this.listeners.clone();
549 }
550 }
551 if (listeners != null && listeners.length > 0) {
552 final FileUploadEvent event = new FileUploadEvent(this, file, null);
553 FileUploadListener listener;
554 for (int i = 0; i < listeners.length; i++) {
555 listener = listeners[i];
556 if (listener != null) {
557 listener.fileUploaded(event);
558 }
559 }
560 }
561 }
562
563 protected final void fireExceptionCaughtEvent(final File file,
564 final Exception exception) {
565 final FileUploadListener[] listeners;
566 synchronized (FileReleaseSystem.class) {
567 if (this.listeners == null) {
568 listeners = null;
569 } else {
570 listeners = (FileUploadListener[])this.listeners.clone();
571 }
572 }
573 if (listeners != null && listeners.length > 0) {
574 final FileUploadEvent event = new FileUploadEvent(this, file, exception);
575 FileUploadListener listener;
576 for (int i = 0; i < listeners.length; i++) {
577 listener = listeners[i];
578 if (listener != null) {
579 listener.exceptionCaught(event);
580 }
581 }
582 }
583 }
584
585
586
587
588
589 private static final void validatePackageName(String packageName)/package-summary.html">g> static final void validatePackageName(String packageName)
590 throws InvalidPackageNameException {
591 if (packageName == null) {/package-summary.html">ong> (packageName == null) {
592 throw new InvalidPackageNameException(new NullPointerException("packageName == null"),
593 "Package names cannot be null");
594 }
595 packageName = packageName.trim();
596 assert packageName != null;
597 if (packageName.length() <= 0) {
598 throw new InvalidPackageNameException("Package names cannot consist " +
599 "solely of whitespace");
600 }
601 }
602
603 private static final void validateFileReleaseName(String releaseName)
604 throws InvalidFileReleaseNameException {
605 if (releaseName == null) {
606 throw new InvalidFileReleaseNameException(new NullPointerException("releaseName == null"),
607 "Release names cannot be null");
608 }
609 releaseName = releaseName.trim();
610 if (releaseName == null || releaseName.length() <= 0) {
611 throw new InvalidFileReleaseNameException("Release names cannot consist " +
612 "solely of whitespace");
613 }
614 }
615
616 private static final void validateProjectID(final int projectID)
617 throws InvalidProjectIDException {
618 if (projectID < 1) {
619 throw new InvalidProjectIDException(String.valueOf(projectID));
620 }
621 }
622
623 private static final void validatePackageID(final int packageID)/package-summary.html">g> static final void validatePackageID(final int packageID)
624 throws InvalidPackageIDException {
625 if (packageID < 1) {
626 throw new InvalidPackageIDException(String/valueOf(packageID))/package-summary.html">trong> new InvalidPackageIDException(String.valueOf(packageID));
627 }
628 }
629
630 private static final void validateFileReleaseID(final int releaseID)
631 throws InvalidFileReleaseIDException {
632 if (releaseID < 1) {
633 throw new InvalidFileReleaseIDException(String.valueOf(releaseID));
634 }
635 }
636
637 private static final void validateAdministrativePage(final WebResponse page,
638 final int projectID)
639 throws InvalidProjectIDException,
640 PermissionDeniedException,
641 SourceForgeException {
642 if (page == null) {
643 throw new SourceForgeException(new NullPointerException("page == null"),
644 "The page argument cannot be null");
645 }
646 String title = null;
647 try {
648 title = page.getTitle();
649 } catch (final SAXException kaboom) {
650 throw new SourceForgeException(kaboom);
651 }
652 if (title == null || title.equals("SourceForge.net: Exiting with Error")) {
653 String text = null;
654 try {
655 text = page.getText();
656 } catch (final IOException kaboom) {
657 throw new SourceForgeException(kaboom);
658 }
659 assert text != null;
660 if (text.indexOf("Access to this page is restricted (either to project members or to project administrators)") >= 0) {
661 throw new PermissionDeniedException("Cannot access administrative " +
662 "interface of project " +
663 projectID);
664 } else {
665 try {
666 throw new SourceForgeException("SourceForge exited with an error: " + page.getText());
667 } catch (final IOException kaboom) {
668 throw new SourceForgeException("SourceForge exited with an error: " + page);
669 }
670 }
671 }
672 }
673
674 private static final void validateFile(final File file)
675 throws InvalidFileException {
676 try {
677 FileSpecification.validate(file);
678 } catch (final Exception kaboom) {
679 throw new InvalidFileException(kaboom, file);
680 }
681 }
682
683
684
685
686
687
688
689
690
691 private final WebResponse getEditReleasePage(final FileRelease release)
692 throws NotLoggedInException,
693 InvalidProjectIDException,
694 InvalidPackageIDException,
695 InvalidFileReleaseIDException,
696 PermissionDeniedException,
697 SourceForgeUIChangeException,
698 SourceForgeException {
699 return this.getEditReleasePage(this.getProjectID(release),
700 this.getPackageID(release),
701 this.getFileReleaseID(release));
702 }
703
704 private final WebResponse getEditReleasePage(final int projectID,
705 final int packageID,
706 final int releaseID)
707 throws NotLoggedInException,
708 InvalidProjectIDException,
709 InvalidPackageIDException,
710 InvalidFileReleaseIDException,
711 PermissionDeniedException,
712 SourceForgeUIChangeException,
713 SourceForgeException {
714 validateProjectID(projectID);
715 validatePackageID(packageID);
716 validateFileReleaseID(releaseID);
717 if (!this.isLoggedIn()) {
718 throw new NotLoggedInException();
719 }
720 final WebConversation wc = this.getWebConversation();
721 assert wc != null;
722 final GetMethodWebRequest request =
723 new GetMethodWebRequest("https://sourceforge.net/project/admin/editreleases.php?package_id=" +
724 packageID +
725 "&release_id=" +
726 releaseID +
727 "&group_id=" +
728 projectID);
729 WebResponse editReleasePage = null;
730 try {
731 editReleasePage = wc.getResponse(request);
732 } catch (final SAXException kaboom) {
733 throw new SourceForgeException(kaboom);
734 } catch (final IOException kaboom) {
735 throw new SourceForgeException(kaboom);
736 }
737 assert editReleasePage != null;
738 validateAdministrativePage(editReleasePage, projectID);
739 return editReleasePage;
740 }
741
742 private final WebResponse getPackagesPage(final int projectID)
743 throws NotLoggedInException,
744 InvalidProjectIDException,
745 PermissionDeniedException,
746 SourceForgeUIChangeException,
747 SourceForgeException {
748 validateProjectID(projectID);
749 if (!this.isLoggedIn()) {
750 throw new NotLoggedInException();
751 }
752 final WebConversation wc = this.getWebConversation();
753 assert wc != null;
754 final GetMethodWebRequest request =
755 new GetMethodWebRequest("http://sourceforge.net/project/admin/editpackages.php?group_id=" +
756 projectID);
757 WebResponse packagesPage = null;
758 try {
759 packagesPage = wc.getResponse(request);
760 } catch (final SAXException kaboom) {
761 throw new SourceForgeException(kaboom);
762 } catch (final IOException kaboom) {
763 throw new SourceForgeException(kaboom);
764 }
765 assert packagesPage != null;
766 validateAdministrativePage(packagesPage, projectID);
767 return packagesPage/package-summary.html">ong> packagesPage;
768 }
769
770 private final WebResponse getReleasesPage(final int projectID,
771 final int packageID)
772 throws NotLoggedInException,
773 InvalidProjectIDException,
774 InvalidPackageIDException,
775 PermissionDeniedException,
776 SourceForgeUIChangeException,
777 SourceForgeException {
778 validateProjectID(projectID);
779 validatePackageID(packageID);
780 if (!this.isLoggedIn()) {
781 throw new NotLoggedInException();
782 }
783 final WebConversation wc = this.getWebConversation();
784 assert wc != null;
785 final GetMethodWebRequest request =
786 new GetMethodWebRequest("http://sourceforge.net/project/admin/editreleases.php?package_id=" +
787 packageID + "&group_id=" + projectID);
788 WebResponse releasesPage = null;
789 try {
790 releasesPage = wc.getResponse(request);
791 } catch (final SAXException kaboom) {
792 throw new SourceForgeException(kaboom);
793 } catch (final IOException kaboom) {
794 throw new SourceForgeException(kaboom);
795 }
796 assert releasesPage != null;
797 validateAdministrativePage(releasesPage, projectID);
798 return releasesPage;
799 }
800
801
802
803
804
805 private final WebForm getEditExistingReleaseForm(final int projectID,
806 final int packageID,
807 final int releaseID)
808 throws NotLoggedInException,
809 InvalidProjectIDException,
810 InvalidPackageIDException,
811 InvalidFileReleaseIDException,
812 PermissionDeniedException,
813 SourceForgeUIChangeException,
814 SourceForgeException {
815 return
816 this.getEditExistingReleaseForm(this.getEditReleasePage(projectID,
817 packageID,
818 releaseID));
819 }
820
821 private final WebForm getEditExistingReleaseForm(final WebResponse editReleasePage)
822 throws NotLoggedInException,
823 SourceForgeUIChangeException,
824 SourceForgeException {
825 if (editReleasePage == null) {
826 return null;
827 }
828 if (!this.isLoggedIn()) {
829 throw new NotLoggedInException();
830 }
831 WebForm[] forms = null;
832 try {
833 forms = this.retainFormsWithAction(editReleasePage.getForms(),
834 "/project/admin/editreleases.php");
835 } catch (final SAXException kaboom) {
836 throw new SourceForgeException(kaboom);
837 }
838 if (forms == null || forms.length <= 0) {
839 return null;
840 }
841 WebForm form;
842 for (int i = 0; i < forms.length; i++) {
843 form = forms[i];
844 if (form != null && form.hasParameterNamed("step1")) {
845 return form;
846 }
847 }
848 return null;
849 }
850
851 private final WebForm getAddFilesForm(final int projectID,
852 final int packageID,
853 final int releaseID)
854 throws NotLoggedInException,
855 InvalidProjectIDException,
856 SourceForgeUIChangeException,
857 SourceForgeException {
858 return this.getAddFilesForm(this.getEditReleasePage(projectID,
859 packageID,
860 releaseID));
861 }
862
863 private final WebForm getAddFilesForm(final WebResponse editReleasePage)
864 throws NotLoggedInException,
865 InvalidProjectIDException,
866 SourceForgeUIChangeException,
867 SourceForgeException {
868 if (editReleasePage == null) {
869 return null;
870 }
871 if (!this.isLoggedIn()) {
872 throw new NotLoggedInException();
873 }
874 WebForm[] forms = null;
875 try {
876 forms = this.retainFormsWithAction(editReleasePage.getForms(),
877 "/project/admin/editreleases.php");
878 } catch (final SAXException kaboom) {
879 throw new SourceForgeException(kaboom);
880 }
881 if (forms == null || forms.length <= 0) {
882 return null;
883 }
884 WebForm form;
885 for (int i = 0; i < forms.length; i++) {
886 form = forms[i];
887 if (form != null && form.hasParameterNamed("step2")) {
888 return form;
889 }
890 }
891 return null;
892 }
893
894 private final WebForm getNotifyOthersForm(final int projectID,
895 final int packageID,
896 final int releaseID)
897 throws NotLoggedInException,
898 InvalidProjectIDException,
899 SourceForgeUIChangeException,
900 SourceForgeException {
901 return this.getNotifyOthersForm(this.getEditReleasePage(projectID,
902 packageID,
903 releaseID));
904 }
905
906 private final WebForm getNotifyOthersForm(final WebResponse editReleasePage)
907 throws NotLoggedInException,
908 InvalidProjectIDException,
909 SourceForgeUIChangeException,
910 SourceForgeException {
911 if (editReleasePage == null) {
912 return null;
913 }
914 if (!this.isLoggedIn()) {
915 throw new NotLoggedInException();
916 }
917 WebForm[] forms = null;
918 try {
919 forms = this.retainFormsWithAction(editReleasePage.getForms(),
920 "/project/admin/editreleases.php");
921 } catch (final SAXException kaboom) {
922 throw new SourceForgeException(kaboom);
923 }
924 if (forms == null || forms.length <= 0) {
925 return null;
926 }
927 WebForm form;
928 for (int i = 0; i < forms.length; i++) {
929 form = forms[i];
930 if (form != null && form.hasParameterNamed("step3")) {
931 return form;
932 }
933 }
934 return null;
935 }
936
937 private final WebForm getUpdatePackageForm(final int projectID,
938 final int packageID)
939 throws NotLoggedInException,
940 InvalidProjectIDException,
941 InvalidPackageIDException,
942 SourceForgeUIChangeException,
943 SourceForgeException {
944 final WebForm[] updatePackageForms = this.getUpdatePackageForms(projectID);
945 if (updatePackageForms != null) {
946 WebForm form;
947 for (int i = 0; i < updatePackageForms.length; i++) {
948 form = updatePackageForms[i];
949 if (form != null &&
950 Integer.toString(packageID).equals(form.getParameterValue("package_id"))) {
951 return form;
952 }
953 }
954 }
955 return null;
956 }
957
958 private final WebForm[] getUpdatePackageForms(final int projectID)
959 throws NotLoggedInException,
960 InvalidProjectIDException,
961 SourceForgeUIChangeException,
962 SourceForgeException {
963 return this.getUpdatePackageForms(this.getPackagesPage(projectID));
964 }
965
966 private final WebForm[] getUpdatePackageForms(final WebResponse packagesPage)/package-summary.html">g> final WebForm[] getUpdatePackageForms(final WebResponse packagesPage)
967 throws NotLoggedInException,
968 InvalidProjectIDException,
969 SourceForgeUIChangeException,
970 SourceForgeException {
971 if (packagesPage == null) {/package-summary.html">ong> (packagesPage == null) {
972 return new WebForm[0];
973 }
974 if (!this.isLoggedIn()) {
975 throw new NotLoggedInException();
976 }
977 WebForm[] allForms = null;
978 try {
979 allForms = packagesPage.getForms();
980 } catch (final SAXException kaboom) {
981 throw new SourceForgeException(kaboom);
982 }
983 final WebForm[] forms =
984 this.retainFormsWithParameterNamed(allForms, "package_id");
985 if (forms == null || forms.length <= 0) {
986 throw new SourceForgeUIChangeException();
987 }
988 return forms;
989 }
990
991
992
993
994
995 private final WebTable getReleaseTable(final int projectID,
996 final int packageID)
997 throws NotLoggedInException,
998 InvalidProjectIDException,
999 InvalidPackageIDException,
1000 PermissionDeniedException,
1001 SourceForgeUIChangeException,
1002 SourceForgeException {
1003 final WebResponse releasesPage = this/getReleasesPage(projectID, packageID)/package-summary.html">ong> WebResponse releasesPage = this.getReleasesPage(projectID, packageID);
1004 assert releasesPage != null;
1005 String text = null;
1006 try {
1007 text = releasesPage.getText();
1008 } catch (final IOException kaboom) {
1009 throw new SourceForgeException(kaboom);
1010 }
1011 if (text == null ||
1012 text.indexOf("You Have No Releases Of This Package Defined") >= 0) {
1013 return null;
1014 }
1015 WebTable releaseTable = null;
1016 try {
1017 releaseTable = releasesPage.getTableStartingWith("Release Name");
1018 } catch (final SAXException kaboom) {
1019 throw new SourceForgeException(kaboom);
1020 }
1021 if (releaseTable == null) {
1022 throw new SourceForgeUIChangeException();
1023 }
1024 return releaseTable;
1025 }
1026
1027
1028
1029
1030
1031 private final int getFileReleaseID(final FileRelease fileRelease)
1032 throws InvalidFileReleaseIDException,
1033 TooManyFileReleasesException,
1034 SourceForgeException {
1035 assertNotNull(fileRelease, "fileRelease");
1036 final String idString = fileRelease.getID();
1037 final int id;
1038 if (idString == null) {
1039 id = this.getFileReleaseID(this.getProjectID(fileRelease),
1040 this.getPackageID(fileRelease),
1041 fileRelease.getName());
1042 } else {
1043 int tempID = -1;
1044 try {
1045 tempID = Integer.parseInt(idString);
1046 } catch (final NumberFormatException kaboom) {
1047 throw new InvalidFileReleaseIDException(kaboom, idString);
1048 } finally {
1049 id = tempID;
1050 }
1051 }
1052 this.validateFileReleaseID(id);
1053 if (idString == null) {
1054 fileRelease.setID(Integer.toString(id));
1055 }
1056 return id;
1057 }
1058
1059 private final int getProjectID(final FileRelease release)
1060 throws InvalidProjectIDException, SourceForgeException {
1061 assertNotNull(release, "release");
1062 final Package pkg = release.getPackage();
1063 assertNotNull(pkg, "pkg");
1064 final Project project = pkg.getProject();
1065 assertNotNull(project, "project");
1066 return this.getProjectID(project);
1067 }
1068
1069 private final int getProjectID(final Package pkg)
1070 throws InvalidProjectIDException, SourceForgeException {
1071 assertNotNull(pkg, "pkg");
1072 final Project project = pkg.getProject();
1073 assertNotNull(project, "project");
1074 return this.getProjectID(project);
1075 }
1076
1077 private final int getProjectID(final Project project)
1078 throws InvalidProjectIDException, SourceForgeException {
1079 assertNotNull(project, "project");
1080 final String idString = project.getID();
1081 final int id;
1082 if (idString == null) {
1083 id = this.getProjectID(project.getShortName());
1084 } else {
1085 int tempID = -1;
1086 try {
1087 tempID = Integer.parseInt(idString);
1088 } catch (final NumberFormatException kaboom) {
1089 throw new InvalidProjectIDException(kaboom, idString);
1090 } finally {
1091 id = tempID;
1092 }
1093 }
1094 this.validateProjectID(id);
1095 if (idString == null) {
1096 project.setID(Integer.toString(id));
1097 }
1098 return id;
1099 }
1100
1101 private final int getPackageID(final FileRelease release)
1102 throws InvalidPackageIDException,
1103 InvalidPackageNameException,
1104 SourceForgeException {
1105 assertNotNull(release, "release");
1106 final Package pkg = release.getPackage();
1107 assertNotNull(pkg, "pkg");
1108 return this.getPackageID(pkg);
1109 }
1110
1111 private final int getPackageID(final Package pkg)
1112 throws InvalidPackageIDException,
1113 InvalidPackageNameException,
1114 TooManyPackagesException,
1115 SourceForgeException {
1116 assertNotNull(pkg, "pkg");
1117 final String idString = pkg.getID();
1118 final int id;
1119 if (idString == null) {
1120 id = this.getPackageID(this.getProjectID(pkg), pkg.getName());
1121 } else {
1122 int tempID = -1;
1123 try {
1124 tempID = Integer.parseInt(idString);
1125 } catch (final NumberFormatException kaboom) {
1126 throw new InvalidPackageIDException(kaboom, idString);
1127 } finally {
1128 id = tempID;
1129 }
1130 }
1131 this.validatePackageID(id);
1132 if (idString == null) {
1133 pkg.setID(Integer.toString(id));
1134 }
1135 return id;
1136 }
1137
1138 public int[] getPackageIDs(final int projectID)
1139 throws NotLoggedInException,
1140 InvalidProjectIDException,
1141 SourceForgeUIChangeException,
1142 SourceForgeException {
1143 final WebForm[] forms = this.getUpdatePackageForms(projectID);
1144 if (forms == null || forms.length <= 0) {
1145 throw new SourceForgeUIChangeException();
1146 }
1147 final int[] ids = new int[forms.length];
1148 WebForm form;
1149 String id;
1150 Integer integerID;
1151 for (int i = 0; i < forms.length; i++) {
1152 form = forms[i];
1153 assert form != null;
1154 id = form.getParameterValue("package_id");
1155 if (id == null) {
1156 throw new SourceForgeException("Form found with no package ID!");
1157 }
1158 try {
1159 integerID = Integer.valueOf(id);
1160 } catch (final NumberFormatException kaboom) {
1161 throw new SourceForgeException(kaboom);
1162 }
1163 assert integerID != null;
1164 ids[i] = integerID.intValue();
1165 }
1166 return ids;
1167 }
1168
1169 public int getPackageID(final int projectID,
1170 final String packageName)
1171 throws NotLoggedInException,
1172 InvalidProjectIDException,
1173 InvalidPackageNameException,
1174 SourceForgeUIChangeException,
1175 TooManyPackagesException,
1176 SourceForgeException {
1177 return this/getPackageID(projectID, packageName, false)/package-summary.html">ong> this.getPackageID(projectID, packageName, false);
1178 }
1179
1180 public int getPackageID(final int projectID,
1181 final String packageName,
1182 final boolean create)
1183 throws NotLoggedInException,
1184 InvalidProjectIDException,
1185 InvalidPackageNameException,
1186 SourceForgeUIChangeException,
1187 TooManyPackagesException,
1188 SourceForgeException {
1189 final int[] ids = this/getPackageIDs(projectID, packageName, create)/package-summary.html">ong> int[] ids = this.getPackageIDs(projectID, packageName, create);
1190 if (ids == null) {
1191 return -1;
1192 } else if (ids.length == 1) {
1193 return ids[0];
1194 } else if (ids.length <= 0) {
1195 return -1;
1196 } else {
1197 throw new TooManyPackagesException(packageName)/package-summary.html">trong> new TooManyPackagesException(packageName);
1198 }
1199 }
1200
1201 public int[] getPackageIDs(final int projectID,
1202 final String packageName)
1203 throws NotLoggedInException,
1204 InvalidProjectIDException,
1205 InvalidPackageNameException,
1206 SourceForgeUIChangeException,
1207 SourceForgeException {
1208 return this/getPackageIDs(projectID, packageName, false)/package-summary.html">ong> this.getPackageIDs(projectID, packageName, false);
1209 }
1210
1211 public int[] getPackageIDs(final int projectID,
1212 final String packageName,
1213 final boolean create)
1214 throws NotLoggedInException,
1215 InvalidProjectIDException,
1216 InvalidPackageNameException,
1217 SourceForgeUIChangeException,
1218 SourceForgeException {
1219 final WebForm[] forms = this.getUpdatePackageForms(projectID);
1220 if (forms == null || forms.length <= 0) {
1221 throw new SourceForgeUIChangeException();
1222 }
1223 return this/getPackageIDs(projectID, forms, packageName, create)/package-summary.html">ong> this.getPackageIDs(projectID, forms, packageName, create);
1224 }
1225
1226 private final int[] getPackageIDs(final int projectID,
1227 final WebForm[] forms,
1228 final String packageName,
1229 final boolean create)
1230 throws NotLoggedInException,
1231 InvalidProjectIDException,
1232 InvalidPackageNameException,
1233 SourceForgeUIChangeException,
1234 SourceForgeException {
1235 this.validatePackageName(packageName);
1236 if (forms == null || forms.length <= 0) {
1237 return new int[0];
1238 }
1239 WebForm form;
1240 String name;
1241 String value;
1242 Integer valueInteger;
1243 final SortedSet values = new TreeSet(REVERSE_COMPARATOR);
1244 for (int i = 0; i < forms.length; i++) {
1245 form = forms[i];
1246 if (form != null) {
1247 name = form.getParameterValue("package_name");
1248 if (name != null && name.equals(packageName)) {
1249 value = form.getParameterValue("package_id");
1250 if (value != null) {
1251 try {
1252 valueInteger = Integer.valueOf(value);
1253 } catch (final NumberFormatException ignore) {
1254 continue;
1255 }
1256 if (valueInteger != null) {
1257 values.add(valueInteger);
1258 }
1259 }
1260 }
1261 }
1262 }
1263 if (values.isEmpty()) {
1264 if (create) {
1265 final</strong> int id = this.createPackage(projectID, packageName, false);
1266 if (id >= 0) {
1267 return new int[] { id };
1268 }
1269 }
1270 return new int[0];
1271 }
1272 final int[] returnMe = new int[values.size()];
1273 final Iterator iterator = values.iterator();
1274 assert iterator != null;
1275 int i = 0;
1276 while (iterator.hasNext()) {
1277 valueInteger = (Integer)iterator.next();
1278 assert valueInteger != null;
1279 returnMe[i++] = valueInteger.intValue();
1280 }
1281 return returnMe;
1282 }
1283
1284 private final int[] getPackageIDs(final int projectID,
1285 final WebResponse packagesPage,
1286 final String packageName,
1287 final boolean create)
1288 throws NotLoggedInException,
1289 InvalidProjectIDException,
1290 InvalidPackageNameException,
1291 SourceForgeUIChangeException,
1292 SourceForgeException {
1293 final WebForm[] forms = this/getUpdatePackageForms(packagesPage)/package-summary.html">ong> WebForm[] forms = this.getUpdatePackageForms(packagesPage);
1294 if (forms == null || forms.length <= 0) {
1295 return new int[0];
1296 }
1297 return this/getPackageIDs(projectID, forms, packageName, create)/package-summary.html">ong> this.getPackageIDs(projectID, forms, packageName, create);
1298 }
1299
1300 public int[] getFileReleaseIDs(final int projectID,
1301 final int packageID)
1302 throws NotLoggedInException,
1303 InvalidProjectIDException,
1304 InvalidPackageIDException,
1305 SourceForgeUIChangeException,
1306 SourceForgeException {
1307 LOGGER.info("Retrieving file release IDs for project " +
1308 projectID +
1309 " and package " + packageID);
1310 final WebTable releaseTable = this/getReleaseTable(projectID, packageID)/package-summary.html">ong> WebTable releaseTable = this.getReleaseTable(projectID, packageID);
1311 if (releaseTable == null) {
1312 return new int[0];
1313 }
1314 final int rowCount = releaseTable.getRowCount();
1315 final SortedSet ids = new TreeSet(REVERSE_COMPARATOR);
1316 TableCell cell;
1317 String cellText;
1318 WebLink link;
1319 String linkURL;
1320 Matcher matcher;
1321 String releaseIDString;
1322 for (int i = 1; i < rowCount; i++) {
1323 cell = releaseTable.getTableCell(i, 0);
1324 if (cell != null) {
1325 link = cell.getLinkWith("[Edit This Release]");
1326 if (link != null) {
1327 linkURL = link.getURLString();
1328 if (linkURL != null) {
1329 releaseIDString = null;
1330 synchronized (PATTERN_LOCK) {
1331 matcher = RELEASE_ID_PATTERN.matcher(linkURL);
1332 if (matcher != null && matcher.find()) {
1333 releaseIDString = matcher.group(1);
1334 }
1335 }
1336 if (releaseIDString != null) {
1337 try {
1338 ids.add(Integer.valueOf(releaseIDString));
1339 } catch (final NumberFormatException ignore) {
1340 continue;
1341 }
1342 }
1343 }
1344 }
1345 }
1346 }
1347 if (ids.isEmpty()) {
1348 return new int[0];
1349 }
1350 final int[] returnMe = new int[ids.size()];
1351 final Iterator iterator = ids.iterator();
1352 assert iterator != null;
1353 for (int i = 0; iterator.hasNext(); i++) {
1354 returnMe[i] = ((Integer)iterator.next()).intValue();
1355 }
1356 return returnMe;
1357 }
1358
1359 public int getFileReleaseID(final int projectID,
1360 final int packageID,
1361 final String fileReleaseName)
1362 throws NotLoggedInException,
1363 InvalidProjectIDException,
1364 InvalidPackageIDException,
1365 SourceForgeUIChangeException,
1366 TooManyFileReleasesException,
1367 SourceForgeException {
1368 return this/getFileReleaseID(projectID, packageID, fileReleaseName, false)/package-summary.html">ong> this.getFileReleaseID(projectID, packageID, fileReleaseName, false);
1369 }
1370
1371 public int getFileReleaseID(final int projectID,
1372 final int packageID,
1373 final String fileReleaseName,
1374 final boolean create)
1375 throws NotLoggedInException,
1376 InvalidProjectIDException,
1377 InvalidPackageIDException,
1378 SourceForgeUIChangeException,
1379 TooManyFileReleasesException,
1380 SourceForgeException {
1381 final int[] ids =
1382 this.getFileReleaseIDs(projectID, packageID, fileReleaseName, create);
1383 if (ids == null || ids.length <= 0) {
1384 return -1;
1385 } else if (ids.length == 1) {
1386 return ids[0];
1387 } else {
1388 throw new TooManyFileReleasesException(fileReleaseName);
1389 }
1390 }
1391
1392 public int[] getFileReleaseIDs(final int projectID,
1393 final int packageID,
1394 final String fileReleaseName)
1395 throws NotLoggedInException,
1396 InvalidProjectIDException,
1397 InvalidPackageIDException,
1398 SourceForgeUIChangeException,
1399 SourceForgeException {
1400 return this/getFileReleaseIDs(projectID, packageID, fileReleaseName, false)/package-summary.html">ong> this.getFileReleaseIDs(projectID, packageID, fileReleaseName, false);
1401 }
1402
1403 public int[] getFileReleaseIDs(final int projectID,
1404 final int packageID,
1405 final String fileReleaseName,
1406 final boolean create)
1407 throws NotLoggedInException,
1408 InvalidProjectIDException,
1409 InvalidPackageIDException,
1410 SourceForgeUIChangeException,
1411 SourceForgeException {
1412 final WebTable releaseTable = this/getReleaseTable(projectID, packageID)/package-summary.html">ong> WebTable releaseTable = this.getReleaseTable(projectID, packageID);
1413 if (releaseTable == null) {
1414 return new int[0];
1415 }
1416 TableCell cell;
1417 String cellText;
1418 String releaseName;
1419 int leftBracketIndex;
1420 WebLink link;
1421 String linkURL;
1422 Matcher matcher;
1423 String releaseIDString;
1424 Integer idInteger;
1425 final SortedSet values = new TreeSet(REVERSE_COMPARATOR);
1426 final int rowCount = releaseTable.getRowCount();
1427 for (int i = 1; i < rowCount; i++) {
1428 cell = releaseTable.getTableCell(i, 0);
1429 if (cell == null) {
1430 continue;
1431 }
1432 cellText = cell.asText();
1433 if (cellText == null) {
1434 continue;
1435 }
1436 leftBracketIndex = cellText.indexOf("[Edit This Release]");
1437 if (leftBracketIndex < 0) {
1438 continue;
1439 }
1440 releaseName = cellText.substring(0, leftBracketIndex);
1441 if (releaseName == null) {
1442 continue;
1443 }
1444 releaseName = releaseName.trim();
1445 assert releaseName != null;
1446 if (releaseName.equals(fileReleaseName)) {
1447 link = cell.getLinkWith("[Edit This Release]");
1448 if (link != null) {
1449 linkURL = link.getURLString();
1450 if (linkURL != null) {
1451 releaseIDString = null;
1452 synchronized (PATTERN_LOCK) {
1453 matcher = RELEASE_ID_PATTERN.matcher(linkURL);
1454 if (matcher != null && matcher.find()) {
1455 releaseIDString = matcher.group(1);
1456 }
1457 }
1458 if (releaseIDString != null) {
1459 try {
1460 idInteger = Integer.valueOf(releaseIDString);
1461 } catch (final NumberFormatException ignore) {
1462 idInteger = null;
1463 }
1464 if (idInteger != null) {
1465 values.add(idInteger);
1466 }
1467 }
1468 }
1469 }
1470 }
1471 }
1472 if (values.isEmpty()) {
1473 if (create) {
1474 final int id =
1475 this.createFileRelease(projectID, packageID, fileReleaseName, false);
1476 if (id >= 0) {
1477 return new int[] { id };
1478 }
1479 }
1480 return new int[0];
1481 }
1482 final Iterator iterator = values.iterator();
1483 assert iterator != null;
1484 final int[] returnMe = new int[values.size()];
1485 int i = 0;
1486 while (iterator.hasNext()) {
1487 idInteger = (Integer)iterator.next();
1488 assert idInteger != null;
1489 returnMe[i++] = idInteger.intValue();
1490 }
1491 return returnMe;
1492 }
1493
1494
1495
1496
1497
1498 public int createFileRelease(final int projectID,
1499 final int packageID,
1500 final String releaseName,
1501 final boolean checkIfExists)
1502 throws NotLoggedInException,
1503 InvalidProjectIDException,
1504 PermissionDeniedException,
1505 SourceForgeUIChangeException,
1506 SourceForgeException {
1507 this.validateProjectID(projectID);
1508 this.validatePackageID(packageID);
1509 this.validateFileReleaseName(releaseName);
1510 if (!this.isLoggedIn()) {
1511 throw new NotLoggedInException();
1512 }
1513 if (checkIfExists) {
1514 final int[] ids =
1515 this.getFileReleaseIDs(projectID, packageID, releaseName, false);
1516 if (ids != null && ids.length > 0) {
1517 return -1;
1518 }
1519 }
1520 final WebConversation wc = this.getWebConversation();
1521 assert wc != null;
1522 GetMethodWebRequest request = null;
1523 try {
1524 request =
1525 new GetMethodWebRequest("http://sourceforge.net/project/admin/newrelease.php?package_id=" +
1526 packageID +
1527 "&group_id=" +
1528 projectID +
1529 "&release_name=" +
1530 URLEncoder.encode(releaseName, "UTF-8") +
1531 "&submit=Create+This+Release");
1532 } catch (final UnsupportedEncodingException wontHappen) {
1533 throw new SourceForgeException(wontHappen);
1534 }
1535 WebResponse response = null;
1536 try {
1537 response = wc.getResponse(request);
1538 } catch (final IOException kaboom) {
1539 throw new SourceForgeException(kaboom);
1540 } catch (final SAXException kaboom) {
1541 throw new SourceForgeException(kaboom);
1542 }
1543 assert response != null;
1544 validateAdministrativePage(response, projectID);
1545 final URL url = response.getURL();
1546 assert url != null;
1547 final String queryString = url.getQuery();
1548 assert queryString != null;
1549 final String releaseIDString;
1550 synchronized (PATTERN_LOCK) {
1551 final Matcher matcher = RELEASE_ID_PATTERN.matcher(queryString);
1552 if (matcher != null && matcher.find()) {
1553 releaseIDString = matcher.group(1);
1554 } else {
1555 releaseIDString = null;
1556 }
1557 }
1558 if (releaseIDString != null) {
1559 try {
1560 return Integer.parseInt(releaseIDString);
1561 } catch (final NumberFormatException kaboom) {
1562 throw new SourceForgeUIChangeException();
1563 }
1564 }
1565 return -1;
1566 }
1567
1568 public int createPackage(final int projectID,
1569 final String packageName,
1570 final boolean checkIfExists)
1571 throws NotLoggedInException,
1572 InvalidProjectIDException,
1573 InvalidPackageNameException,
1574 PermissionDeniedException,
1575 SourceForgeUIChangeException,
1576 SourceForgeException {
1577 this.validateProjectID(projectID);
1578 this.validatePackageName(packageName);
1579 if (!this.isLoggedIn()) {
1580 throw new NotLoggedInException();
1581 }
1582 if (checkIfExists) {
1583 final int[] ids = this/getPackageIDs(projectID, packageName, false)/package-summary.html">trong> int[] ids = this.getPackageIDs(projectID, packageName, false);
1584 if (ids != null && ids.length > 0) {
1585 return -1;
1586 }
1587 }
1588 final WebConversation wc = this.getWebConversation();
1589 assert wc != null;
1590 GetMethodWebRequest request = null;
1591 try {
1592 request =
1593 new GetMethodWebRequest("http://sourceforge.net/project/admin/editpackages.php?group_id=" +
1594 projectID +
1595 "&func=add_package&package_name=" +
1596 URLEncoder.encode(packageName, "UTF-8") +
1597 "&submit=Create+This+Package");
1598 } catch (final UnsupportedEncodingException wontHappen) {
1599 throw new SourceForgeException(wontHappen);
1600 }
1601 WebResponse response = null;
1602 try {
1603 response = wc.getResponse(request);
1604 } catch (final IOException kaboom) {
1605 throw new SourceForgeException(kaboom);
1606 } catch (final SAXException kaboom) {
1607 throw new SourceForgeException(kaboom);
1608 }
1609 assert response != null;
1610 validateAdministrativePage(response, projectID);
1611 String text = null;
1612 try {
1613 text = response.getText();
1614 } catch (final IOException kaboom) {
1615 throw new SourceForgeException(kaboom);
1616 }
1617 assert text != null;
1618 if (text.indexOf("Added Package") < 0) {
1619 throw new SourceForgeUIChangeException();
1620 }
1621 final int[] ids =
1622 this.getPackageIDs(projectID, response, packageName, false);
1623 if (ids == null || ids.length <= 0) {
1624 return -1;
1625 }
1626 return ids[0];
1627 }
1628
1629
1630
1631
1632 public String getPackageName(final int projectID,
1633 final int packageID)
1634 throws NotLoggedInException,
1635 InvalidProjectIDException,
1636 InvalidPackageIDException,
1637 SourceForgeException {
1638 final WebForm updatePackageForm =
1639 this.getUpdatePackageForm(projectID, packageID);
1640 final String packageName/package-summary.html">ong> String packageName;
1641 if (updatePackageForm != null) {
1642 packageName = updatePackageForm.getParameterValue("package_name");
1643 } else {
1644 packageName = null;
1645 }
1646 if (packageName == null) {/package-summary.html">ong> (packageName == null) {
1647 throw new InvalidPackageIDException(packageID)/package-summary.html">trong> new InvalidPackageIDException(packageID);
1648 }
1649 return packageName/package-summary.html">ong> packageName;
1650 }
1651
1652 public void setPackageVisible(final Package pkg)
1653 throws NotLoggedInException,
1654 TooManyPackagesException,
1655 SourceForgeException {
1656 assertNotNull(pkg, "pkg");
1657 this.setPackageVisible(this.getProjectID(pkg),
1658 this.getPackageID(pkg),
1659 !pkg.isHidden());
1660 }
1661
1662 public void setPackageVisible(final int projectID,
1663 final int packageID,
1664 final boolean visible)
1665 throws NotLoggedInException,
1666 SourceForgeException {
1667 this.setPackageVisible(projectID,
1668 packageID,
1669 this/getPackageName(projectID, packageID),/package-summary.html">>.getPackageName(projectID, packageID),
1670 visible);
1671 }
1672
1673 private final void setPackageVisible(final int projectID,
1674 final int packageID,
1675 final String packageName,
1676 final boolean visible)
1677 throws NotLoggedInException,
1678 PermissionDeniedException,
1679 InvalidProjectIDException,
1680 InvalidPackageIDException,
1681 InvalidPackageNameException,
1682 SourceForgeUIChangeException,
1683 SourceForgeException {
1684 validateProjectID(projectID);
1685 validatePackageID(packageID);
1686 validatePackageName(packageName);
1687 if (!this.isLoggedIn()) {
1688 throw new NotLoggedInException();
1689 }
1690 final WebConversation wc = this.getWebConversation();
1691 assert wc != null;
1692 final String status;
1693 if (visible) {
1694 status = "3";
1695 } else {
1696 status = "1";
1697 }
1698 GetMethodWebRequest request = null;
1699 try {
1700
1701
1702
1703 request =
1704 new GetMethodWebRequest("http://sourceforge.net/project/admin/editpackages.php?group_id=" +
1705 projectID +
1706 "&func=update_package&package_name=" +
1707 URLEncoder.encode(packageName, "UTF-8") +
1708 "&status_id=" +
1709 status +
1710 "&submit=Update");
1711 } catch (final UnsupportedEncodingException wontHappen) {
1712 throw new SourceForgeException(wontHappen);
1713 }
1714 WebResponse response = null;
1715 try {
1716 response = wc.getResponse(request);
1717 } catch (final IOException kaboom) {
1718 throw new SourceForgeException(kaboom);
1719 } catch (final SAXException kaboom) {
1720 throw new SourceForgeException(kaboom);
1721 }
1722 assert response != null;
1723 validateAdministrativePage(response, projectID);
1724 if (!visible) {
1725 String text = null;
1726 try {
1727 text = response.getText();
1728 } catch (final IOException kaboom) {
1729 throw new SourceForgeException(kaboom);
1730 }
1731 if (text == null ||
1732 text.indexOf("Sorry - you cannot hide a package that contains active " +
1733 "releases") >= 0) {
1734
1735 final</strong> int[] ids = this.getFileReleaseIDs(projectID, packageID);
1736 if (ids != null && ids.length > 0) {
1737 for (int i = 0; i < ids.length; i++) {
1738 this.setFileReleaseVisible(projectID, packageID, ids[i], false);
1739 }
1740 }
1741
1742 this.setPackageVisible(projectID, packageID, packageName, false);
1743 }
1744 }
1745 }
1746
1747 public void setFileReleaseVisible(final FileRelease release)
1748 throws NotLoggedInException,
1749 TooManyPackagesException,
1750 TooManyFileReleasesException,
1751 SourceForgeUIChangeException,
1752 SourceForgeException {
1753 assertNotNull(release, "release");
1754 this.setFileReleaseVisible(this.getProjectID(release),
1755 this.getPackageID(release),
1756 this.getFileReleaseID(release),
1757 !release.isHidden());
1758 }
1759
1760 /***
1761 * Ensures that the file release with the supplied identifier is visible or
1762 * hidden.
1763 *
1764 * @param projectID
1765 * the project identifier to which the file release in question
1766 * belongs; must be a positive integer greater than
1767 * <code>0</code>
1768 * @param packageID
1769 * the package identifier to which the file release in question
1770 * belongs; must be a positive integer greater than
1771 * <code>0</code>
1772 * @param releaseID
1773 * the identifier of the file release in question; must be a
1774 * positive integer greater than <code>0</code>
1775 * @param visible
1776 * whether the file release is to be visible or not
1777 * @exception SourceForgeException
1778 * if an error occurs
1779 */
1780 public void setFileReleaseVisible(final int projectID,
1781 final int packageID,
1782 final int releaseID,
1783 final boolean visible)
1784 throws SourceForgeException {
1785 final WebForm editExistingReleaseForm =
1786 this.getEditExistingReleaseForm(projectID,
1787 packageID,
1788 releaseID);
1789 if (editExistingReleaseForm != null) {
1790 if (visible) {
1791 editExistingReleaseForm.setParameter("status_id", "1");
1792 } else {
1793 editExistingReleaseForm.setParameter("status_id", "3");
1794 }
1795 try {
1796 editExistingReleaseForm.submit();
1797 } catch (final SAXException kaboom) {
1798 throw new SourceForgeException(kaboom);
1799 } catch (final IOException kaboom) {
1800 throw new SourceForgeException(kaboom);
1801 }
1802 }
1803 }
1804
1805 /***
1806 * A convenience method that uploads the supplied {@link File}s to the
1807 * <code>incoming</code> directory on <code>upload.sourceforge.net</code> by
1808 * calling the {@link #uploadFile(File)} method on each element of the array.
1809 *
1810 * @param files
1811 * the {@link File}s to upload; may be <code>null</code> or
1812 * empty in which case no action will be taken
1813 */
1814 public void uploadFiles(final File[] files) {
1815 if (files == null || files.length <= 0) {
1816 return;
1817 }
1818 File file;
1819 for (int i = 0; i < files.length; i++) {
1820 file = files[i];
1821 if (file != null) {
1822 this.uploadFile(file);
1823 }
1824 }
1825 }
1826
1827 /***
1828 * Uploads the supplied {@link File} via FTP to the <code>incoming</code>
1829 * directory on <code>upload.sourceforge.net</code>. This implementation
1830 * calls the {@link #uploadFileSynchronously(File)} method, but could be
1831 * overridden to call the {@link #uploadFileAsynchronously(File)} method
1832 * instead.
1833 *
1834 * @param file
1835 * the {@link File} to be uploaded; must not be
1836 * <code>null</code>
1837 */
1838 public void uploadFile(final File file) {
1839 this.uploadFileSynchronously(file);
1840 }
1841
1842 /***
1843 * Uploads the supplied {@link File} via FTP to the <code>incoming</code>
1844 * directory on <code>upload.sourceforge.net</code> and returns immediately.
1845 * Any {@linkplain #addFileUploadListener(FileUploadListener) registered
1846 * <code>FileUploadListener</code>}s are notified of a {@linkplain
1847 * FileUploadListener#fileUploaded(FileUploadEvent) successful upload} or any
1848 * {@linkplain FileUploadListener#exceptionCaught(FileUploadEvent)
1849 * <code>Exception</code> that is encountered} before this method returns.
1850 *
1851 * <p>This method calls the {@link #uploadFileSynchronously(File)} method in
1852 * the body of a new {@link Thread}'s {@link Thread#run()} method.</p>
1853 *
1854 * @param file
1855 * the {@link File} to be uploaded; must not be
1856 * <code>null</code>
1857 */
1858 public final void uploadFileAsynchronously(final File file) {
1859 new Thread() {
1860 public final void run() {
1861 uploadFileSynchronously(file);
1862 }
1863 }.start();
1864 }
1865
1866 /***
1867 * Uploads the supplied {@link File} via FTP to the <code>incoming</code>
1868 * directory on <code>upload.sourceforge.net</code>, blocking until the {@link
1869 * File} is uploaded successfully. Any {@linkplain
1870 * #addFileUploadListener(FileUploadListener) registered
1871 * <code>FileUploadListener</code>}s are notified of a {@linkplain
1872 * FileUploadListener#fileUploaded(FileUploadEvent) successful upload} or any
1873 * {@linkplain FileUploadListener#exceptionCaught(FileUploadEvent)
1874 * <code>Exception</code> that is encountered} before this method returns.
1875 *
1876 * @param file
1877 * the {@link File} to be uploaded; must not be
1878 * <code>null</code>
1879 */
1880 public final void uploadFileSynchronously(final File file) {
1881 InputStream inputStream = null;
1882 OutputStream outputStream = null;
1883 Exception exception = null;
1884 try {
1885 validateFile(file);
1886 LOGGER.info("Uploading " + file);
1887 inputStream = new BufferedInputStream(new FileInputStream(file));
1888 final String shortName = file.getName();
1889 assert shortName != null;
1890 final URLConnection connection =
1891 new URL("ftp://upload.sourceforge.net/incoming/" +
1892 shortName).openConnection();
1893 assert connection != null;
1894 connection.setDoOutput(true);
1895 connection.connect();
1896 outputStream = connection.getOutputStream();
1897 if (!(outputStream instanceof BufferedOutputStream)) {
1898 outputStream = new BufferedOutputStream(outputStream);
1899 }
1900 copyStream(inputStream, outputStream);
1901 this.fireFileUploadedEvent(file);
1902 LOGGER.info("Successfully uploaded " + file);
1903 } catch (final Exception kaboom) {
1904 exception = kaboom;
1905 this.fireExceptionCaughtEvent(file, kaboom);
1906 } finally {
1907 try {
1908 if (outputStream != null) {
1909 outputStream.close();
1910 }
1911 } catch (final IOException ignore) {
1912
1913 }
1914 try {
1915 if (inputStream != null) {
1916 inputStream.close();
1917 }
1918 } catch (final IOException ignore) {
1919
1920 }
1921 if (exception != null) {
1922 LOGGER.severe("Failed to upload " + file + ": " + exception);
1923 }
1924 }
1925 }
1926
1927 private synchronized final void ensureNoUploadErrors() throws SourceForgeException {
1928 if (this.uploadException != null) {
1929 if (this.uploadException instanceof SourceForgeException) {
1930 throw (SourceForgeException)this.uploadException;
1931 }
1932 throw new SourceForgeException(this.uploadException);
1933 }
1934 }
1935
1936 /***
1937 * Assembles a {@link SortedSet} of {@linkplain File#getName()
1938 * <code>File</code> "short name"s} from the supplied {@link FileRelease}.
1939 * This method never returns <code>null</code>.
1940 *
1941 * @param release
1942 * the {@link FileRelease} to work on; must not be
1943 * <code>null</code>
1944 * @return a {@link SortedSet} of {@linkplain File#getName()
1945 * <code>File</code> "short name"s} from the supplied {@link
1946 * FileRelease}
1947 */
1948 private static final SortedSet extractShortFileNames(final FileRelease release) {
1949 final SortedSet names = new TreeSet();
1950 if (release != null) {
1951 names.addAll(Arrays.asList(release.getShortFileNames()));
1952 }
1953 return Collections.unmodifiableSortedSet(names);
1954 }
1955
1956
1957 /***
1958 * Copies the supplied {@link InputStream} to the supplied {@link
1959 * OutputStream} in chunks of 1,024 bytes.
1960 *
1961 * @param inputStream
1962 * the {@link InputStream} to copy; must not be
1963 * <code>null</code>
1964 * @param outputStream
1965 * the {@link OutputStream} to copy the supplied {@link
1966 * InputStream} to; must not be <code>null</code>
1967 * @exception IOException
1968 * if an input/output error occurs
1969 */
1970 private static final void copyStream(final InputStream inputStream,
1971 final OutputStream outputStream)
1972 throws IOException {
1973 if (inputStream == null || outputStream == null) {
1974 return;
1975 }
1976 final byte[] buffer = new byte[1024];
1977 int numberOfBytesRead = 0;
1978 while ((numberOfBytesRead = inputStream.read(buffer)) != -1) {
1979 outputStream.write(buffer, 0, numberOfBytesRead);
1980 }
1981 outputStream.flush();
1982 }
1983
1984 private void login(final FileRelease release)
1985 throws InvalidCredentialsException, SourceForgeException {
1986 if (this.isLoggedIn()) {
1987 return;
1988 }
1989 assertNotNull(release, "release");
1990 final Package pkg = release.getPackage();
1991 assertNotNull(pkg, "pkg");
1992 final Project project = pkg.getProject();
1993 assertNotNull(project, "project");
1994 final Administrator admin = project.getAdministrator();
1995 assertNotNull(admin, "admin");
1996 final String userName = admin.getName();
1997 assertNotNull(userName, "userName");
1998 this.login(userName, admin.getPassword());
1999 }
2000
2001 /***
2002 * A {@link Comparator} that provides the inverse of the result of comparing
2003 * two {@link Comparable}s according to their natural ordering. This is a
2004 * fancy way of saying this class deliberately behaves exactly backwards,
2005 * i.e. common-sense "smaller" values are sorted as though they were "larger".
2006 *
2007 * <p>This class exists solely so that a method like {@link
2008 * FileReleaseSystem#getFileReleaseIDs(int, int, String)} will return the
2009 * most-recently-created identifier as the first element in the
2010 * <code>int</code> array it returns.</p>
2011 *
2012 * @author <a href="mailto:ljnelson94@alumni.amherst.edu">Laird Nelson</a>
2013 * @version $Revision: 1.21 $ $Date: 2004/07/27 20:20:35 $
2014 * @since July 18, 2003
2015 */
2016 static final class ReverseComparator implements Comparator {
2017
2018 /***
2019 * Creates a new {@link FileReleaseSystem.ReverseComparator}.
2020 */
2021 ReverseComparator() {
2022 super();
2023 }
2024
2025 /***
2026 * Compares the first argument to the second. Returns a negative integer,
2027 * zero, or a positive integer as the first argument is <i>greater</i> than,
2028 * equal to, or less than the second.
2029 *
2030 * @param one
2031 * the first {@link Object} to compare; must be an instance
2032 * of {@link Comparable}
2033 * @param two
2034 * the second {@link Object} to compare; must be an instance
2035 * of {@link Comparable}
2036 * @return a negative integer, zero, or a positive integer as the first
2037 * argument is <i>greater</i> than, equal to, or less than the
2038 * second
2039 * @exception NullPointerException
2040 * if <code>one</code> is <code>null</code>
2041 * @exception ClassCastException
2042 * if <code>one</code> is not an instance of {@link Comparable}
2043 */
2044 public final int compare(final Object one, final Object two)
2045 throws NullPointerException, ClassCastException {
2046 return -((Comparable)one).compareTo(two);
2047 }
2048
2049 /***
2050 * Tests the supplied {@link Object} to see if it is equal to this {@link
2051 * FileReleaseSystem.ReverseComparator}. All instances of {@link
2052 * FileReleaseSystem.ReverseComparator} are considered equal.
2053 *
2054 * @param anObject
2055 * the {@link Object} to test; may be <code>null</code>
2056 * @return <code>true</code> if the supplied {@link Object} is an
2057 * instance of {@link FileReleaseSystem.ReverseComparator}
2058 */
2059 public final boolean equals(final Object anObject) {
2060 return anObject == this || anObject instanceof ReverseComparator;
2061 }
2062
2063 }
2064
2065 }