001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2016 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 */ 028package edu.wisc.ssec.mcidasv.supportform; 029 030import java.io.ByteArrayInputStream; 031import java.io.File; 032import java.io.InputStream; 033import java.util.ArrayList; 034import java.util.List; 035 036import org.apache.commons.httpclient.Header; 037import org.apache.commons.httpclient.HttpClient; 038import org.apache.commons.httpclient.methods.PostMethod; 039import org.apache.commons.httpclient.methods.multipart.FilePart; 040import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; 041import org.apache.commons.httpclient.methods.multipart.Part; 042import org.apache.commons.httpclient.methods.multipart.PartSource; 043import org.apache.commons.httpclient.methods.multipart.StringPart; 044 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048import ucar.unidata.util.IOUtil; 049//import ucar.unidata.util.Misc; 050import ucar.unidata.util.WrapperException; 051 052import 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 */ 058public 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 // tack on the contents of runMcV.prefs 193 parts.add(buildRealFilePart("form_data[att_prefs]", form.getPrefsPath())); 194 195 Part[] arr = parts.toArray(new Part[0]); 196 MultipartRequestEntity mpr = new MultipartRequestEntity(arr, method.getParams()); 197 method.setRequestEntity(mpr); 198 return method; 199 } 200 201 /** 202 * Attempt to POST contents of support request form to {@link #requestUrl}. 203 * 204 * @throws WrapperException if there was a problem on the server. 205 */ 206 protected String compute() { 207 // logic ripped from the IDV's HttpFormEntry#doPost(List, String) 208 try { 209 while ((tryCount++ < POST_ATTEMPTS) && !isCancelled()) { 210 method = buildPostMethod(validFormUrl, form); 211 HttpClient client = new HttpClient(); 212 client.executeMethod(method); 213 if (method.getStatusCode() >= 300 && method.getStatusCode() <= 399) { 214 Header location = method.getResponseHeader("location"); 215 if (location == null) { 216 return "Error: No 'location' given on the redirect"; 217 } 218 validFormUrl = location.getValue(); 219 if (method.getStatusCode() == 301) { 220 logger.warn("form post has been permanently moved to: {}", validFormUrl); 221 } 222 continue; 223 } 224 break; 225 } 226 return IOUtil.readContents(method.getResponseBodyAsStream()); 227 } catch (Exception e) { 228 throw new WrapperException(POST_ERROR, e); 229 } 230 } 231 232// protected String compute() { 233// try { 234// Misc.sleep(2000); 235// return "dummy success!"; 236// } catch (Exception e) { 237// throw new WrapperException(POST_ERROR, e); 238// } 239// } 240 241 /** 242 * Handles completion of a support request. 243 * 244 * @param result Result of {@link #compute()}. 245 * @param exception Exception thrown from {@link #compute()}, if any. 246 * @param cancelled Whether or not the user opted to cancel. 247 */ 248 @Override protected void onCompletion(String result, Throwable exception, boolean cancelled) { 249 logger.trace("result={} exception={} cancelled={}", new Object[] { result, exception, cancelled }); 250 if (cancelled) { 251 return; 252 } 253 254 if (exception == null) { 255 form.showSuccess(); 256 } else { 257 form.showFailure(exception.getMessage()); 258 } 259 } 260 261}