001 /*
002 * $Id: Submitter.java,v 1.9 2012/02/19 17:35:50 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2012
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 *
012 * All Rights Reserved
013 *
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.
016 *
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 *
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
025 * GNU Lesser Public License for more details.
026 *
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program. If not, see http://www.gnu.org/licenses.
029 */
030 package edu.wisc.ssec.mcidasv.supportform;
031
032 import java.io.ByteArrayInputStream;
033 import java.io.File;
034 import java.io.InputStream;
035 import java.util.ArrayList;
036 import java.util.List;
037
038 import org.apache.commons.httpclient.Header;
039 import org.apache.commons.httpclient.HttpClient;
040 import org.apache.commons.httpclient.methods.PostMethod;
041 import org.apache.commons.httpclient.methods.multipart.FilePart;
042 import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
043 import org.apache.commons.httpclient.methods.multipart.Part;
044 import org.apache.commons.httpclient.methods.multipart.PartSource;
045 import org.apache.commons.httpclient.methods.multipart.StringPart;
046
047 import org.slf4j.Logger;
048 import org.slf4j.LoggerFactory;
049
050 import ucar.unidata.util.IOUtil;
051 import ucar.unidata.util.WrapperException;
052
053 import edu.wisc.ssec.mcidasv.util.BackgroundTask;
054
055 public class Submitter extends BackgroundTask<String> {
056
057 private static final Logger logger = LoggerFactory.getLogger(Submitter.class);
058
059 /** We'll follow up to this many redirects for {@code requestUrl}. */
060 private static final int POST_ATTEMPTS = 5;
061
062 /** Used to gather user input and system information. */
063 private final SupportForm form;
064
065 /** URL that we'll attempt to {@code POST} our requests at.*/
066 private final String requestUrl = "https://www.ssec.wisc.edu/mcidas/misc/mc-v/supportreq/support.php";
067
068 /** Keeps track of the most recent redirect for {@code requestUrl}. */
069 private String validFormUrl = requestUrl;
070
071 /** Number of redirects we've tried since starting. */
072 private int tryCount = 0;
073
074 /** Handy reference to the status code (and more) of our {@code POST}. */
075 private PostMethod method = null;
076
077 public Submitter(final SupportForm form) {
078 this.form = form;
079 }
080
081 /**
082 * Creates a file attachment that's based upon a real file.
083 *
084 * @param id The parameter ID. Usually something like
085 * {@literal "form_data[att_two]"}.
086 * @param file Path to the file that's going to be attached.
087 *
088 * @return {@code POST}-able file attachment using the name and contents of
089 * {@code file}.
090 */
091 private static FilePart buildRealFilePart(final String id, final String file) {
092 return new FilePart(id, new PartSource() {
093 public InputStream createInputStream() {
094 try {
095 return IOUtil.getInputStream(file);
096 } catch (Exception e) {
097 throw new WrapperException("Reading file: "+file, e);
098 }
099 }
100 public String getFileName() {
101 return new File(file).getName();
102 }
103 public long getLength() {
104 return new File(file).length();
105 }
106 });
107 }
108
109 /**
110 * Creates a file attachment that isn't based upon an actual file. Useful
111 * for something like the {@literal "extra"} attachment where you collect
112 * a bunch of data but don't want to deal with creating a temporary file.
113 *
114 * @param id Parameter ID. Typically something like
115 * {@literal "form_data[att_extra]"}.
116 * @param file Fake name of the file. Can be whatever you like.
117 * @param data The actual data to place inside the attachment.
118 *
119 * @return {@code POST}-able file attachment using a spoofed filename!
120 */
121 private static FilePart buildFakeFilePart(final String id, final String file, final byte[] data) {
122 return new FilePart(id, new PartSource() {
123 public InputStream createInputStream() {
124 return new ByteArrayInputStream(data);
125 }
126 public String getFileName() {
127 return file;
128 }
129 public long getLength() {
130 return data.length;
131 }
132 });
133 }
134
135 /**
136 * Attempts to {@code POST} to {@code url} using the information from
137 * {@code form}.
138 *
139 * @param url URL that'll accept the {@code POST}. Typically
140 * {@link #requestUrl}.
141 * @param form The {@link SupportForm} that contains the data to use in the
142 * support request.
143 *
144 * @return Big honkin' object that contains the support request.
145 */
146 private static PostMethod buildPostMethod(String url, SupportForm form) {
147 PostMethod method = new PostMethod(url);
148
149 List<Part> parts = new ArrayList<Part>();
150 parts.add(new StringPart("form_data[fromName]", form.getUser()));
151 parts.add(new StringPart("form_data[email]", form.getEmail()));
152 parts.add(new StringPart("form_data[organization]", form.getOrganization()));
153 parts.add(new StringPart("form_data[subject]", form.getSubject()));
154 parts.add(new StringPart("form_data[description]", form.getDescription()));
155 parts.add(new StringPart("form_data[submit]", ""));
156 parts.add(new StringPart("form_data[p_version]", "p_version=ignored"));
157 parts.add(new StringPart("form_data[opsys]", "opsys=ignored"));
158 parts.add(new StringPart("form_data[hardware]", "hardware=ignored"));
159 parts.add(new StringPart("form_data[cc_user]", Boolean.toString(form.getSendCopy())));
160
161 // attach the files the user has explicitly attached.
162 if (form.hasAttachmentOne()) {
163 parts.add(buildRealFilePart("form_data[att_two]", form.getAttachmentOne()));
164 }
165 if (form.hasAttachmentTwo()) {
166 parts.add(buildRealFilePart("form_data[att_three]", form.getAttachmentTwo()));
167 }
168 // if the user wants, attach an XML bundle of the state
169 if (form.canBundleState() && form.getSendBundle()) {
170 parts.add(buildFakeFilePart("form_data[att_state]", form.getBundledStateName(), form.getBundledState()));
171 }
172
173 // attach system properties
174 parts.add(buildFakeFilePart("form_data[att_extra]", form.getExtraStateName(), form.getExtraState()));
175
176 if (form.canSendLog()) {
177 parts.add(buildRealFilePart("form_data[att_log]", form.getLogPath()));
178 }
179
180 Part[] arr = parts.toArray(new Part[0]);
181 MultipartRequestEntity mpr = new MultipartRequestEntity(arr, method.getParams());
182 method.setRequestEntity(mpr);
183 return method;
184 }
185
186 protected String compute() {
187 // logic ripped from the IDV's HttpFormEntry#doPost(List, String)
188 try {
189 while ((tryCount++ < POST_ATTEMPTS) && !isCancelled()) {
190 method = buildPostMethod(validFormUrl, form);
191 HttpClient client = new HttpClient();
192 client.executeMethod(method);
193 if (method.getStatusCode() >= 300 && method.getStatusCode() <= 399) {
194 Header location = method.getResponseHeader("location");
195 if (location == null) {
196 return "Error: No 'location' given on the redirect";
197 }
198 validFormUrl = location.getValue();
199 if (method.getStatusCode() == 301) {
200 logger.warn("form post has been permanently moved to: {}", validFormUrl);
201 }
202 continue;
203 }
204 break;
205 }
206 return IOUtil.readContents(method.getResponseBodyAsStream());
207 } catch (Exception e) {
208 throw new WrapperException("doing post", e);
209 }
210 }
211
212 @Override protected void onCompletion(String result, Throwable exception, boolean cancelled) {
213 logger.trace("result={} exception={} cancelled={}", new Object[] { result, exception, cancelled });
214 if (cancelled) {
215 return;
216 }
217
218 if (exception == null) {
219 form.showSuccess();
220 } else {
221 form.showFailure(exception.getMessage());
222 }
223 }
224
225 }