Source code: com/clra/web/MembershipServlet.java
1 /*
2 * Copyright (c) Carnegie Lake Rowing Association 2002. All rights reserved.
3 * Distributed under the GPL license. See doc/COPYING.
4 * $RCSfile: MembershipServlet.java,v $
5 * $Date: 2003/03/05 13:55:13 $
6 * $Revision: 1.2 $
7 */
8
9 package com.clra.web;
10
11 import com.clra.web.MemberView;
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.io.InputStreamReader;
15 import java.io.OutputStream;
16 import java.io.PrintWriter;
17 import java.io.StringBufferInputStream;
18 import java.io.StringWriter;
19 import java.io.Writer;
20 import java.net.MalformedURLException;
21 import java.net.Socket;
22 import java.net.SocketException;
23 import java.net.UnknownHostException;
24 import java.net.URL;
25 import javax.ejb.NoSuchEntityException;
26 import javax.servlet.ServletConfig;
27 import javax.servlet.ServletException;
28 import javax.servlet.http.HttpServlet;
29 import javax.servlet.http.HttpServletRequest;
30 import javax.servlet.http.HttpServletResponse;
31 import javax.xml.transform.Source;
32 import javax.xml.transform.Transformer;
33 import javax.xml.transform.TransformerConfigurationException;
34 import javax.xml.transform.TransformerException;
35 import javax.xml.transform.TransformerFactory;
36 import javax.xml.transform.stream.StreamSource;
37 import javax.xml.transform.stream.StreamResult;
38 import org.apache.axis.encoding.Base64;
39 import org.apache.log4j.Category;
40 import org.apache.log4j.helpers.Loader;
41
42 /**
43 * Converts a soap response to CSV or beautified XML message.
44 * The only valid request parameters are:<ul>
45 * <li><code>format</code>.
46 * Specifies what action to perform.<br>
47 * The only valid values for this parameter are:<ul>
48 * <li><code>CSV</code><br>
49 * Converts a SOAP message to comma-separarted-value format.</li>
50 * <li><code>XML</code><br>
51 * Converts a SOAP message to a simplified XML format.</li></ul></p>
52 * <p>
53 * If the <code>format</code> parameter is omitted, the output format
54 * defaults to beautified XML.</p>
55 *
56 * @version $Revision: 1.2 $
57 */
58 public class MembershipServlet extends HttpServlet {
59
60 private final static String base = MembershipServlet.class.getName();
61 private final static Category theLog = Category.getInstance( base );
62
63 /** Socket timeout (milliseconds) */
64 private final static int timeoutMillis = 60000;
65
66 /** Default buffer size */
67 private final static int BUFSIZE = 100*1024;
68
69 /** The name of the "format" request parameter */
70 public final static String RPN_FORMAT = "format";
71
72 /** The "CSV" value for the "format" request parameter */
73 public final static String RPV_FORMAT_CSV = "CSV";
74
75 /** The "XML" value for the "format" request parameter */
76 public final static String RPV_FORMAT_XML = "XML";
77
78 /** The file that holds the pre-built SOAP "findAll" request headers */
79 public final static String FILE_FINDALL_HEADERS =
80 "com/clra/web/findAll-request-headers.http";
81
82 /** The file that holds the pre-built SOAP "findAll" request content */
83 public final static String FILE_FINDALL_CONTENT =
84 "com/clra/web/findAll-request-content.http";
85
86 /** The file that holds the CVS stylesheet */
87 public final static String FILE_CSV_STYLESHEET =
88 "com/clra/web/csv.xsl";
89
90 /** The file that holds the simplified-XML stylesheet */
91 public final static String FILE_XML_STYLESHEET =
92 "com/clra/web/memberXml.xsl";
93
94 /** The pre-built "findAll" request headers */
95 private final static String requestHeaders =
96 loadStringFromFile( FILE_FINDALL_HEADERS );
97
98 /** The pre-built "findAll" request message */
99 private final static String requestContent =
100 loadStringFromFile( FILE_FINDALL_CONTENT );
101
102 /** The csv stylesheet */
103 private final static String csvStylesheet =
104 loadStringFromFile( FILE_CSV_STYLESHEET );
105
106 /** The simplified-XML stylesheet */
107 private final static String xmlStylesheet =
108 loadStringFromFile( FILE_XML_STYLESHEET );
109
110 private static String loadStringFromFile( final String strFile ) {
111
112 String retVal = null;
113 try {
114 URL url = Loader.getResource( strFile, MembershipServlet.class );
115 retVal = loadStringFromInputStream( url.openStream() );
116 if ( theLog.isDebugEnabled() ) {
117 final int total = retVal.length();
118 theLog.debug( "read " + strFile + " bytes from " + strFile );
119 theLog.debug( "'" + strFile + "' contents == '" + retVal + "'" );
120 }
121 }
122 catch( Exception x ){
123 String msg = "unable to load request message from '" + strFile + "'";
124 theLog.fatal(msg,x);
125 throw new IllegalStateException( msg );
126 }
127
128 return retVal;
129 } // loadStringFromFile(String)
130
131 // Closes input stream after string is loaded
132 private static String loadStringFromInputStream( InputStream is )
133 throws IOException {
134 StringWriter out = new StringWriter( BUFSIZE );
135 writeFromInputStreamToWriter( is, out );
136 return out.toString();
137 }
138
139 /** An implementation-specific exception that indicates a SOAP problem */
140 public static class MembershipSoapException extends Exception {
141 private MembershipSoapException( String msg ) { super(msg); }
142 };
143
144 /** Initializes the servlet */
145 public void init(ServletConfig config) throws ServletException {
146 super.init(config);
147 }
148
149 /** Destroys the servlet */
150 public void destroy() {
151 }
152
153 private static Socket getFindAllResponseSocket(
154 String username, String password ) throws IOException,
155 MalformedURLException, SocketException, UnknownHostException,
156 NoSuchEntityException {
157
158 username = username == null ? null : username.trim() ;
159 password = password == null ? null : password.trim() ;
160
161 Socket retVal = null;
162
163 // Create the request message
164 String requestMessage = null;
165 if ( username != null && password != null ) {
166 String authInfo = username + ":" + password;
167 String encoded = Base64.encode( authInfo.getBytes() );
168 StringBuffer sb = new StringBuffer();
169 sb.append( requestHeaders );
170 sb.append( "Authorization: Basic " );
171 sb.append( encoded );
172 sb.append( "\r\n\r\n" );
173 sb.append( requestContent );
174 requestMessage = new String(sb);
175 } else {
176 StringBuffer sb = new StringBuffer();
177 sb.append( requestHeaders );
178 sb.append( "\r\n" );
179 sb.append( requestContent );
180 requestMessage = new String(sb);
181 }
182
183 // Send the SOAP request
184 final String strURL = Configuration.SOAP_SERVER_URL;
185 URL url = new URL( strURL );
186 retVal = new Socket( url.getHost(), url.getPort() );
187 retVal.setSoTimeout( timeoutMillis );
188 OutputStream os = retVal.getOutputStream();
189 final byte[] buf = requestMessage.getBytes();
190 final int total = buf.length;
191 os.write( buf, 0, total );
192 theLog.debug( "wrote " + total + " bytes to " + strURL );
193
194 return retVal;
195 } // getFindAllResponseSocket()
196
197 // Assumes buf != null && buf.length > 3
198 private static boolean foundCRNL_CRNL( char[] buf ) {
199 return ( buf[0] == '\r' )
200 && ( buf[1] == '\n' )
201 && ( buf[2] == '\r' )
202 && ( buf[3] == '\n' );
203 }
204
205 private static void readUntilEndOfHttpHeaders( InputStream is )
206 throws IOException, MembershipSoapException {
207
208 final int EOS = -1;
209
210 char[] buf = new char[4]; // initialized to all zero's
211
212 int b = is.read();
213 while ( b != EOS ) {
214
215 buf[0] = buf[1];
216 buf[1] = buf[2];
217 buf[2] = buf[3];
218 buf[3] = (char) b;
219 if ( foundCRNL_CRNL(buf) ) {
220 break;
221 }
222
223 b = is.read();
224 } // while !EOS
225
226 if ( b == EOS ) {
227 throw new MembershipSoapException( "did not find end of HTTP headers" );
228 }
229
230 return;
231 } // readUntilEndOfHttpHeaders(InputStream)
232
233 /**
234 * Processes requests for both HTTP <code>GET</code> and <code>POST</code>
235 * methods. The current implementation uses a pre-built SOAP message
236 * stored in the text file specified by <code>FINDALL-REQUEST</code>
237 * to retrieve a SOAP message, and then uses XSL stylesheets to
238 * to simplify the SOAP XML.
239 *
240 * @param request servlet request
241 * @param response servlet response
242 */
243 protected void processRequest(
244 HttpServletRequest request, HttpServletResponse response)
245 throws ServletException, java.io.IOException {
246
247 // Determine how to format the response
248 String strFormat = request.getParameter( RPN_FORMAT );
249 if ( strFormat == null ) {
250 theLog.debug( "null format defaults to XML" );
251 strFormat = RPV_FORMAT_XML;
252 }
253
254 // Set the response content-type and get the appropriate stylesheet
255 String xsl = null;
256 if ( RPV_FORMAT_CSV.equalsIgnoreCase(strFormat) ) {
257 theLog.debug( RPV_FORMAT_CSV );
258 response.setContentType( "text/plain" );
259 xsl = csvStylesheet;
260 }
261 else if (RPV_FORMAT_XML.equalsIgnoreCase(strFormat) ) {
262 theLog.debug( RPV_FORMAT_XML );
263 response.setContentType( "text/plain" );
264 xsl = xmlStylesheet;
265 }
266 else {
267 String msg = "ERROR: unexpected format == '" + strFormat + "'";
268 theLog.error( msg );
269 throw new ServletException( msg );
270 }
271
272 try {
273 MemberView mv = MemberTag.getMemberFromAuthenticatedUser(request);
274 final String username = mv.getAccountName();
275 final String password = mv.getAccountPassword();
276
277 PrintWriter out = response.getWriter();
278 writeResponse( username, password, xsl, out );
279 }
280 catch( Exception x ) {
281 theLog.error( x );
282 throw new ServletException( x );
283 }
284
285 return;
286 } // processRequest(HttpServletRequest,HttpServletResponse)
287
288 /** Delegates to the <code>processRequest</code> method */
289 protected void doGet(
290 HttpServletRequest request, HttpServletResponse response)
291 throws ServletException, java.io.IOException {
292 processRequest(request, response);
293 }
294
295 /** Delegates to the <code>processRequest</code> method */
296 protected void doPost(
297 HttpServletRequest request, HttpServletResponse response)
298 throws ServletException, java.io.IOException {
299 processRequest(request, response);
300 }
301
302 /**
303 * Returns a short description of the servlet.
304 */
305 public String getServletInfo() {
306 return "Reformats a SOAP message to CSV or simplified XML";
307 }
308
309 /**
310 * Writes the entire SOAP response for <code>Member.findAllMembers</code>
311 * to the specified PrintWriter. Used for JUnit testing.
312 */
313 public static void
314 writeFindAllSoapXmlResponse(String username,String password,PrintWriter out)
315 throws Exception {
316 writeResponse(username,password,null,out);
317 } // getFindAllSoapXmlResponse()
318
319 /**
320 * Writes the CSV-formatted response for <code>Member.findAllMembers</code>
321 * to the specified PrintWriter. Used for JUnit testing.
322 */
323 public static void
324 writeFindAllCsvResponse(String username,String password,PrintWriter out)
325 throws Exception {
326 writeResponse(username,password,csvStylesheet,out);
327 }
328
329 /**
330 * Returns a simplified XML-formatted response for
331 * <code>Member.findAllMembers</code>. Used for JUnit testing.
332 */
333 public static void
334 writeFindAllSimplifiedXmlResponse(String name,String pass,PrintWriter out)
335 throws Exception {
336 writeResponse(name,pass,xmlStylesheet,out);
337 }
338
339 // Does NOT close the Writer.
340 private static void
341 writeResponse(String username, String password, String xsl, PrintWriter out)
342 throws TransformerConfigurationException, TransformerException, IOException,
343 MembershipSoapException {
344
345 // Precondition
346 if ( out == null ) {
347 throw new IllegalArgumentException( "null PrintWriter" );
348 }
349
350 Socket s = null;
351 InputStream soapStream = null;
352
353 try {
354
355 s = getFindAllResponseSocket(username,password);
356 soapStream = s.getInputStream();
357
358 if ( xsl == null ) {
359 writeFromInputStreamToWriter( soapStream, out );
360 }
361 else {
362 readUntilEndOfHttpHeaders( soapStream );
363 Source xmlSource = new StreamSource( soapStream );
364 Source xslSource =
365 new StreamSource( new StringBufferInputStream(xsl) );
366 TransformerFactory f = TransformerFactory.newInstance();
367 Transformer t = f.newTransformer( xslSource );
368 t.transform( xmlSource, new StreamResult(out) );
369 }
370 out.flush();
371
372 }
373 finally {
374 if( s != null ) {
375 try { s.close(); } catch( Exception x ) {}
376 }
377 }
378
379 return;
380 } // getFindAllFormattedResponse(String)
381
382 // Closes input stream after string is loaded.
383 // Does NOT close the Writer.
384 private static void writeFromInputStreamToWriter(InputStream is, Writer out)
385 throws IOException {
386
387 InputStreamReader isr = null;
388 try {
389 isr = new InputStreamReader( is );
390 char[] cbuf = new char[BUFSIZE];
391 int numRead = isr.read( cbuf, 0, cbuf.length );
392 while ( numRead > 0 ) {
393 out.write( cbuf, 0, numRead );
394 numRead = isr.read( cbuf, 0, cbuf.length );
395 }
396 out.flush();
397 }
398 finally {
399 if ( isr != null ) {
400 try { isr.close(); } catch( Exception x ) {}
401 }
402 if ( is != null ) {
403 try { is.close(); } catch( Exception x ) {}
404 is = null;
405 }
406 }
407
408 return;
409 } // readFromInputStreamToOutputWriter(InputStream,Writer)
410
411 } // MembershipServlet
412
413 /*
414 * $Log: MembershipServlet.java,v $
415 * Revision 1.2 2003/03/05 13:55:13 rphall
416 * Added username/password to SOAP call
417 *
418 * Revision 1.1 2003/03/04 02:16:33 rphall
419 * Moved MembershipServlet and *.xsl files to com/clra/web
420 *
421 * Revision 1.1 2003/03/02 17:31:43 rphall
422 * Converts a soap response to CSV or beautified XML message
423 *
424 */
425