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 }