001    /*
002     * $Id: ServerAction.java,v 1.7 2005/11/22 13:31:13 kleopatra Exp $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    package org.jdesktop.swingx.action;
022    
023    import java.awt.event.ActionEvent;
024    import java.io.BufferedReader;
025    import java.io.ByteArrayOutputStream;
026    import java.io.IOException;
027    import java.io.InputStreamReader;
028    import java.io.PrintWriter;
029    import java.net.HttpURLConnection;
030    import java.net.MalformedURLException;
031    import java.net.URL;
032    import java.net.URLConnection;
033    import java.net.UnknownHostException;
034    import java.security.AccessControlException;
035    import java.util.HashMap;
036    import java.util.Iterator;
037    import java.util.Map;
038    import java.util.Set;
039    import java.util.logging.Level;
040    import java.util.logging.Logger;
041    
042    import javax.swing.AbstractAction;
043    import javax.swing.Action;
044    import javax.swing.Icon;
045    
046    //import org.jdesktop.swing.Application;
047    
048    
049    /**
050     * An action which will invoke an http POST operation.
051     *
052     * @author Mark Davidson
053     */
054    public class ServerAction extends AbstractAction {
055        // Server action support
056        private static final Logger LOG = Logger.getLogger(ServerAction.class
057                .getName());
058        private static final String PARAMS = "action-params";
059        private static final String HEADERS = "action-headers";
060        private static final String URL = "action-url";
061    
062        private static final String URL_CACHE = "_URL-CACHE__";
063    
064        public ServerAction() {
065        this("action");
066        }
067    
068        public ServerAction(String name) {
069        super(name);
070        }
071    
072        /**
073         * @param name display name of the action
074         * @param command the value of the action command key
075         */
076        public ServerAction(String name, String command) {
077        this(name, command, null);
078        }
079    
080        public ServerAction(String name, Icon icon) {
081        super(name, icon);
082        }
083    
084        /**
085         * @param name display name of the action
086         * @param command the value of the action command key
087         * @param icon icon to display
088         */
089        public ServerAction(String name, String command, Icon icon) {
090        super(name, icon);
091        putValue(Action.ACTION_COMMAND_KEY, command);
092        }
093    
094        /**
095         * Set the url for the action.
096         * <p>
097         * @param url a string representation of the url
098         */
099        public void setURL(String url) {
100        putValue(URL, url);
101        putValue(URL_CACHE, null);
102        }
103    
104        public String getURL() {
105        return (String)getValue(URL);
106        }
107    
108        private Map getParams() {
109        return (Map)getValue(PARAMS);
110        }
111    
112        private void setParams(Map params) {
113        putValue(PARAMS, params);
114        }
115    
116        /**
117         * Adds a name value pair which represents a url parameter in an http
118         * POST request.
119         */
120        public void addParam(String name, String value) {
121        Map params = getParams();
122        if (params == null) {
123            params = new HashMap();
124            setParams(params);
125        }
126        params.put(name, value);
127        }
128    
129        /**
130         * Return a parameter value corresponding to name or null if it doesn't exist.
131         */
132        public String getParamValue(String name) {
133        Map params = getParams();
134        return params == null ? null : (String)params.get(name);
135        }
136    
137        /**
138         * Return a set of parameter names or null if there are no params
139         */
140        public Set getParamNames() {
141        Map params = getParams();
142        return params == null ? null : params.keySet();
143        }
144    
145        private Map getHeaders() {
146        return (Map)getValue(HEADERS);
147        }
148    
149        private void setHeaders(Map headers) {
150        putValue(HEADERS, headers);
151        }
152    
153        /**
154         * Adds a name value pair which represents a url connection request property.
155         * For example, name could be "Content-Type" and the value could be
156         * "application/x-www-form-urlencoded"
157         */
158        public void addHeader(String name, String value) {
159        Map map = getHeaders();
160        if (map == null) {
161            map = new HashMap();
162            setHeaders(map);
163        }
164        map.put(name, value);
165        }
166    
167        /**
168         * Return a header value corresponding to name or null if it doesn't exist.
169         */
170        public String getHeaderValue(String name) {
171        Map headers = getHeaders();
172        return headers == null ? null : (String)headers.get(name);
173        }
174    
175        /**
176         * Return a set of parameter names or null if there are no params
177         */
178        public Set getHeaderNames() {
179        Map headers = getHeaders();
180        return headers == null ? null : headers.keySet();
181        }
182    
183        /**
184         * Invokes the server operation when the action has been invoked.
185         */
186        public void actionPerformed(ActionEvent evt) {
187        URL execURL = (URL)getValue(URL_CACHE);
188        if (execURL == null && !"".equals(getURL())) {
189            try {
190            String url = getURL();
191            if (url.startsWith("http")) {
192                execURL = new URL(url);
193            } else {
194            }
195            if (execURL == null) {
196                // XXX TODO: send a message
197                return;
198            } else {
199                // Cache this value.
200                putValue(URL_CACHE, execURL);
201            }
202    
203            } catch (MalformedURLException ex) {
204            LOG.log(Level.WARNING, "something went wrong...", ex);
205            }
206        }
207    
208        try {
209            URLConnection uc = execURL.openConnection();
210    
211            // Get all the header name/value pairs ans set the request headers
212            Set headerNames = getHeaderNames();
213            if (headerNames != null && !headerNames.isEmpty()) {
214            Iterator iter = headerNames.iterator();
215            while (iter.hasNext()) {
216                String name = (String)iter.next();
217                uc.setRequestProperty(name, getHeaderValue(name));
218            }
219            }
220            uc.setUseCaches(false);
221            uc.setDoOutput(true);
222    
223            ByteArrayOutputStream byteStream = new ByteArrayOutputStream(512);
224            PrintWriter out = new PrintWriter(byteStream, true);
225            out.print(getPostData());
226            out.flush();
227    
228            // POST requests must have a content-length.
229            String length = String.valueOf(byteStream.size());
230            uc.setRequestProperty("Content-length", length);
231    
232            // Write POST data to real output stream.
233            byteStream.writeTo(uc.getOutputStream());
234    
235            BufferedReader buf = null;
236            if (uc instanceof HttpURLConnection) {
237            HttpURLConnection huc = (HttpURLConnection)uc;
238            int code = huc.getResponseCode();
239            String message = huc.getResponseMessage();
240    
241            // Handle the result.
242            if (code < 400) {
243                // action succeeded send to status bar
244                // XXX TODO: setStatusMessage(createMessage(code, message));
245                // Format the response
246                // TODO: This should load asychnonously
247                buf = new BufferedReader(new InputStreamReader(uc.getInputStream()));
248    
249            } else {
250                // action has failed show dialog
251                // XXX TODO: setStatusMessage(createMessage(code, message));
252                buf = new BufferedReader(new InputStreamReader(huc.getErrorStream()));
253            }
254            String line;
255    
256            StringBuffer buffer = new StringBuffer();
257            while ((line = buf.readLine()) != null) {
258                // RG: Fix for J2SE 5.0; Can't cascade append() calls because
259                // return type in StringBuffer and AbstractStringBuilder are different
260                buffer.append(line);
261                        buffer.append('\n');
262            }
263                // JW: this used the Debug - maybe use finest level?    
264                LOG.finer("returned from connection\n" + buffer.toString());    
265            }
266        } catch (UnknownHostException ex) {
267                LOG.log(Level.WARNING, "UnknownHostException detected. Could it be a proxy issue?", ex);
268                
269        } catch (AccessControlException ex) {
270                LOG.log(Level.WARNING, "AccessControlException detected", ex);
271        } catch (IOException ex) {
272                LOG.log(Level.WARNING, "IOException detected", ex);
273        }
274        }
275    
276        /**
277         * Retrieves a string which represents the parameter data for a server action.
278         * @return a string of name value pairs prefixed by a '?' and delimited by an '&'
279         */
280        private String getPostData() {
281        // Write the data into local buffer
282        StringBuffer postData = new StringBuffer();
283    
284        // TODO: the action should be configured to retrieve the data.
285    
286        // Get all the param name/value pairs and build the data string
287        Set paramNames = getParamNames();
288        if (paramNames != null && !paramNames.isEmpty()) {
289            Iterator iter = paramNames.iterator();
290            try {
291                while (iter.hasNext()) {
292                    String name = (String) iter.next();
293                    postData.append('&').append(name).append('=');
294                    postData.append(getParamValue(name));
295                }
296            }
297            catch (Exception ex) {  // RG: append(char) throws IOException in J2SE 5.0
298                /** @todo Log it */
299            }
300            // Replace the first & with a ?
301            postData.setCharAt(0, '?');
302        }
303        
304        LOG.finer("ServerAction: POST data: " + postData.toString());
305        return postData.toString();
306        }
307    
308    
309        /**
310         * Creates a human readable message from the server code and message result.
311         * @param code an http error code.
312         * @param msg server message
313         */
314        private String createMessage(int code, String msg) {
315        StringBuffer buffer = new StringBuffer("The action \"");
316        buffer.append(getValue(NAME));
317    
318        if (code < 400) {
319            buffer.append("\" has succeeded ");
320        } else {
321            buffer.append("\" has failed\nPlease check the Java console for more details.\n");
322        }
323        // RG: Fix for J2SE 5.0; Can't cascade append() calls because
324        // return type in StringBuffer and AbstractStringBuilder are different
325        buffer.append("\nServer response:\nCode: ");
326        buffer.append(code);
327        buffer.append(" Message: ");
328        buffer.append(msg);
329    
330        return buffer.toString();
331        }
332    }