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