1   /**
2    * Copyright (C) 2005-2009 Alfresco Software Limited.
3    *
4    * This file is part of the Spring Surf Extension project.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.springframework.extensions.webscripts;
20  
21  import java.io.BufferedReader;
22  import java.io.ByteArrayOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.InputStreamReader;
26  import java.io.PrintStream;
27  import java.io.PrintWriter;
28  import java.io.StringWriter;
29  import java.io.UnsupportedEncodingException;
30  import java.net.MalformedURLException;
31  import java.net.URL;
32  import java.util.HashMap;
33  import java.util.Map;
34  
35  import org.junit.Ignore;
36  import org.springframework.beans.BeansException;
37  import org.springframework.beans.factory.InitializingBean;
38  import org.springframework.context.ApplicationContext;
39  import org.springframework.context.ApplicationContextAware;
40  import org.springframework.context.MessageSource;
41  import org.springframework.context.support.ClassPathXmlApplicationContext;
42  import org.springframework.core.io.ClassPathResource;
43  import org.springframework.extensions.config.Config;
44  import org.springframework.extensions.config.ConfigService;
45  import org.springframework.extensions.config.ServerConfigElement;
46  import org.springframework.extensions.config.ServerProperties;
47  import org.springframework.extensions.surf.util.URLDecoder;
48  import org.springframework.extensions.webscripts.servlet.ServletAuthenticatorFactory;
49  import org.springframework.extensions.webscripts.servlet.WebScriptServletRuntime;
50  import org.springframework.mock.web.MockHttpServletRequest;
51  import org.springframework.mock.web.MockHttpServletResponse;
52  
53  /**
54   * Stand-alone Web Script Test Server
55   * 
56   * @author davidc
57   */
58  @Ignore public class TestWebScriptServer implements ApplicationContextAware, InitializingBean
59  {
60      /** The application context */
61      protected ApplicationContext applicationContext;
62      
63      // dependencies
64      protected ConfigService configService;	
65      protected RuntimeContainer container;
66      protected ServletAuthenticatorFactory authenticatorFactory;
67      
68      /** Server Configuration */
69      protected ServerProperties serverProperties;
70      
71      /** The reader for interaction. */
72      protected BufferedReader fIn;
73      
74      /** Last command issued */
75      protected String lastCommand = null;
76  
77      /** Current user */
78      protected String username = null;
79      
80      /** Current headers */
81      protected Map<String, String> headers = new HashMap<String, String>();
82      
83      /** I18N Messages */
84      protected MessageSource m_messages;    
85      
86      
87      /**
88       * @param configService
89       */
90      public void setConfigService(ConfigService configService)
91      {
92          this.configService = configService;
93      }
94      
95      /**
96       * Sets the Web Script Runtime Context
97       * 
98       * @param container
99       */
100     public void setContainer(RuntimeContainer container)
101     {
102         this.container = container;
103     }
104 
105     /**
106      * @param authenticatoFactory
107      */
108     public void setServletAuthenticatorFactory(ServletAuthenticatorFactory authenticatorFactory)
109     {
110         this.authenticatorFactory = authenticatorFactory;
111     }
112 
113     /**
114      * Sets the Messages resource bundle
115      * 
116      * @param messages
117      * @throws IOException
118      */
119     public void setMessages(MessageSource messages)
120         throws IOException
121     {
122         this.m_messages = messages;
123     }
124     
125     /**
126      * Sets the application context 
127      * 
128      * @param applicationContext    the application context
129      * @throws BeansException
130      */
131     public void setApplicationContext(ApplicationContext applicationContext)
132             throws BeansException
133     {
134         this.applicationContext = applicationContext;
135     }
136 
137     /*
138      * (non-Javadoc)
139      * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
140      */
141     public void afterPropertiesSet() throws Exception
142     {
143         username = getDefaultUserName();
144     }
145 
146     /**
147      * Get default user name
148      */
149     protected String getDefaultUserName()
150     {
151         return "admin";
152     }
153 
154     /**
155      * Gets the server properties
156      * 
157      * @return  server properties
158      * @throws Exception
159      */
160 	public ServerProperties getServerProperties()
161 	{
162 		if (serverProperties == null)
163 		{
164 			Config config = configService.getConfig("Server");
165 			serverProperties = (ServerConfigElement)config.getConfigElement(ServerConfigElement.CONFIG_ELEMENT_ID);
166 		}
167 		return serverProperties;
168 	}
169     
170     /**
171      * Gets the application context
172      * 
173      * @return  ApplicationContext  the application context
174      */
175     public ApplicationContext getApplicationContext()
176     {
177         return this.applicationContext;
178     }
179 
180     
181     /**
182      * Main entry point.
183      */
184     public static void main(String[] args)
185     {
186         try
187         {
188             TestWebScriptServer testServer = getTestServer();
189             testServer.rep();
190         }
191         catch(Throwable e)
192         {
193             StringWriter strWriter = new StringWriter();
194             PrintWriter printWriter = new PrintWriter(strWriter);
195             e.printStackTrace(printWriter);
196             System.out.println(strWriter.toString());
197         }
198         finally
199         {
200             System.exit(0);
201         }
202     }
203 
204     
205     /**
206      * Retrieve an instance of the TestWebScriptServer
207      *  
208      * @return  Test Server
209      */
210     public static TestWebScriptServer getTestServer()
211     {
212         String[] CONFIG_LOCATIONS = new String[]
213         {
214             "classpath:org/springframework/extensions/webscripts/spring-webscripts-application-context.xml", 
215             "classpath:org/springframework/extensions/webscripts/spring-webscripts-application-context-test.xml"
216         };
217         ApplicationContext context = new ClassPathXmlApplicationContext(CONFIG_LOCATIONS);
218         TestWebScriptServer testServer = (TestWebScriptServer)context.getBean("webscripts.test");
219         
220         return testServer;
221     }
222     
223     /**
224      * Submit a Web Script Request.
225      * 
226      * @param req  request
227      * @return  response
228      * @throws IOException
229      */
230     public Response submitRequest(Request req)
231         throws IOException
232     {
233         return submitRequest(req.getMethod(), req.getUri(), req.getHeaders(), req.getBody(), req.getEncoding(), req.getType());
234     }
235     
236     /**
237      * Submit a Web Script Request. 
238      * <p>
239      * Can specifiy content and content type
240      * 
241      * @param method        http method
242      * @param uri           web script (relative to /alfresco/service)
243      * @param headers       headers
244      * @param body          body of request content (can be null)
245      * @param contentType   content type (eg "multipart/form-data") (can be null)
246      * @return              response           
247      * @throws IOException
248      */
249     public Response submitRequest(String method, String uri, Map<String, String> headers, byte[] body, String encoding, String contentType)
250         throws IOException
251     {
252         MockHttpServletRequest req = createMockServletRequest(method, uri);
253         
254         // Set the headers
255         if (headers != null)
256         {
257             for (Map.Entry<String, String> header: headers.entrySet())
258             {
259                 req.addHeader(header.getKey(), header.getValue());
260             }
261         }        
262 
263         // Set the body of the request
264         if (body != null)
265         {            
266             req.setContent(body);
267         }
268         
269         if (encoding != null)
270         {
271             req.setCharacterEncoding(encoding);
272         }
273         
274         // Set the content type
275         if (contentType != null && contentType.length() != 0)
276         {
277             req.setContentType(contentType);
278             req.addHeader("Content-Type", contentType);
279         }
280         
281         MockHttpServletResponse res = new MockHttpServletResponse();
282         AbstractRuntime runtime = new WebScriptServletRuntime(container, authenticatorFactory, req, res, getServerProperties());
283         runtime.executeScript();
284         return new MockHttpServletResponseResponse(res);
285     }
286     
287     /**
288      * Create a Mock HTTP Servlet Request
289      * 
290      * @param method
291      * @param uri
292      * @return  mock http servlet request
293      * @throws UnsupportedEncodingException 
294      * @throws MalformedURLException 
295      */
296     private MockHttpServletRequest createMockServletRequest(String method, String uri)
297         throws UnsupportedEncodingException, MalformedURLException
298     {
299         // extract only path portions of URI, ignore host & port
300         URL url = new URL(new URL("http://localhost"), uri);
301         String path = url.getPath();
302         
303         if (!(path.startsWith("/alfresco/service") || path.startsWith("/a/s")))
304         {
305             path = "/alfresco/service" + path;
306         }
307         
308         MockHttpServletRequest req = new MockHttpServletRequest(method, uri);
309         req.setContextPath("/alfresco");
310         req.setServletPath("/service");
311 
312         if (uri != null)
313         {
314             String queryString = url.getQuery();
315             if (queryString != null && queryString.length() > 0)
316             {
317                 String[] args = queryString.split("&");
318                 for (String arg : args)
319                 {
320                     String[] parts = arg.split("=");
321                     req.addParameter(parts[0], (parts.length == 2) ? URLDecoder.decode(parts[1]) : null);
322                 }
323                 req.setQueryString(URLDecoder.decode(queryString));
324             }
325             String requestURI = path;
326             req.setRequestURI(URLDecoder.decode(requestURI));
327         }
328         
329         return req;
330     }
331 
332     /**
333      * A Read-Eval-Print loop.
334      */
335     public void rep()
336     {
337         // accept commands
338         fIn = new BufferedReader(new InputStreamReader(System.in));
339         while (true)
340         {
341             System.out.print("ok> ");
342             try
343             {
344                 // get command
345                 final String line = fIn.readLine();
346                 if (line == null || line.equals("exit") || line.equals("quit"))
347                 {
348                     return;
349                 }
350                                 
351                 // execute command in context of currently selected user
352                 long startTime = System.nanoTime();
353                 System.out.print(interpretCommand(line));
354                 System.out.println("" + (System.nanoTime() - startTime)/1000000f + "ms");
355             }
356             catch (Exception e)
357             {
358                 e.printStackTrace(System.err);
359                 System.out.println("");
360             }
361         }
362     }
363     
364     /**
365      * Interpret a single command using the BufferedReader passed in for any data needed.
366      * 
367      * @param line The unparsed command
368      * @return The textual output of the command.
369      */
370     protected String interpretCommand(final String line)
371         throws IOException
372     {
373         return executeCommand(line);
374     }
375     
376     /**
377      * Execute a single command using the BufferedReader passed in for any data needed.
378      * 
379      * TODO: Use decent parser!
380      * 
381      * @param line The unparsed command
382      * @return The textual output of the command.
383      */
384     protected String executeCommand(String line)
385         throws IOException
386     {
387         String[] command = line.split(" ");
388         if (command.length == 0)
389         {
390             command = new String[1];
391             command[0] = line;
392         }
393         
394         ByteArrayOutputStream bout = new ByteArrayOutputStream();
395         PrintStream out = new PrintStream(bout);
396 
397         // repeat last command?
398         if (command[0].equals("r"))
399         {
400             if (lastCommand == null)
401             {
402                 return "No command entered yet.";
403             }
404             
405             if (command.length > 1 && command[1].equals("show"))
406             {
407                 return lastCommand + "\n\n";
408             }
409             else
410             {
411                 return "repeating command " + lastCommand + "\n\n" + interpretCommand(lastCommand);
412             }
413         }
414         
415         // remember last command
416         lastCommand = line;
417 
418         // execute command
419         if (command[0].equals("help"))
420         {
421             String helpFile = m_messages.getMessage("testserver.help", null, null);
422             ClassPathResource helpResource = new ClassPathResource(helpFile);
423             byte[] helpBytes = new byte[500];
424             InputStream helpStream = helpResource.getInputStream();
425             try
426             {
427                 int read = helpStream.read(helpBytes);
428                 while (read != -1)
429                 {
430                     bout.write(helpBytes, 0, read);
431                     read = helpStream.read(helpBytes);
432                 }
433             }
434             finally
435             {
436                 helpStream.close();
437             }
438         }
439         
440         else if (command[0].equals("user"))
441         {
442             if (command.length == 2)
443             {
444                 username = command[1];
445             }
446             out.println("using user " + username);
447         }
448         
449         else if (command[0].equals("get") ||
450                  command[0].equals("delete"))
451         {
452             String uri = (command.length > 1) ? command[1] : null;
453             Response res = submitRequest(command[0], uri, headers, null, null, null);
454             bout.write(("Response status: " + res.getStatus()).getBytes());
455             out.println();
456             bout.write(res.getContentAsByteArray());
457             out.println();
458         }
459 
460         else if (command[0].equals("post"))
461         {
462             String uri = (command.length > 1) ? command[1] : null;
463             String contentType = (command.length > 2) ? command[2] : null;
464             String body = "";
465             for (int i = 3; i < command.length; i++)
466             {
467                 body += command[i] + " ";
468             }
469             Response res = submitRequest(command[0], uri, headers, body.getBytes(), null, contentType);
470             bout.write(("Response status: " + res.getStatus()).getBytes());
471             out.println();
472             bout.write(res.getContentAsByteArray());
473             out.println();
474         }
475 
476         else if (command[0].equals("put"))
477         {
478             String uri = (command.length > 1) ? command[1] : null;
479             String contentType = (command.length > 2) ? command[2] : null;
480             String body = "";
481             for (int i = 3; i < command.length; i++)
482             {
483                 body += command[i] + " ";
484             }
485             Response res = submitRequest(command[0], uri, headers, body.getBytes(), null, contentType);
486             bout.write(("Response status: " + res.getStatus()).getBytes());
487             out.println();
488             bout.write(res.getContentAsByteArray());
489             out.println();
490         }
491         
492         else if (command[0].equals("tunnel"))
493         {
494             if (command.length < 4)
495             {
496                 return "Syntax Error.\n";
497             }
498             
499             if (command[1].equals("param"))
500             {
501                 String uri = command[3];
502                 if (uri.indexOf('?') == -1)
503                 {
504                     uri += "?alf:method=" + command[2];
505                 }
506                 else
507                 {
508                     uri += "&alf:method=" + command[2];
509                 }
510                 Response res = submitRequest("post", uri, headers, null, null, null);
511                 bout.write(res.getContentAsByteArray());
512                 out.println();
513             }
514             
515             else if (command[1].equals("header"))
516             {
517                 Map<String, String> tunnelheaders = new HashMap<String, String>();
518                 tunnelheaders.putAll(headers);
519                 tunnelheaders.put("X-HTTP-Method-Override", command[2]);
520                 Response res = submitRequest("post", command[3], tunnelheaders, null, null, null);
521                 bout.write(res.getContentAsByteArray());
522                 out.println();
523             }
524                 
525             else
526             {
527                 return "Syntax Error.\n";
528             }
529         }
530 
531         else if (command[0].equals("header"))
532         {
533             if (command.length == 1)
534             {
535                 for (Map.Entry<String, String> entry : headers.entrySet())
536                 {
537                     out.println(entry.getKey() + " = " + entry.getValue());
538                 }
539             }
540             else if (command.length == 2)
541             {
542                 String[] param = command[1].split("=");
543                 if (param.length == 0)
544                 {
545                     return "Syntax Error.\n";
546                 }
547                 if (param.length == 1)
548                 {
549                     headers.remove(param[0]);
550                     out.println("deleted header " + param[0]);
551                 }
552                 else if (param.length == 2)
553                 {
554                     headers.put(param[0], param[1]);
555                     out.println("set header " + param[0] + " = " + headers.get(param[0]));
556                 }
557                 else
558                 {
559                     return "Syntax Error.\n";
560                 }
561             }
562             else
563             {
564                 return "Syntax Error.\n";
565             }
566         }            
567 
568         else if (command[0].equals("reset"))
569         {
570             container.reset();
571             out.println("Runtime context '" + container.getName() + "' reset.");
572         }
573         
574         else
575         {
576             return "Syntax Error.\n";
577         }
578  
579         out.flush();
580         String retVal = new String(bout.toByteArray());
581         out.close();
582         return retVal;
583     }
584 
585     
586     /**
587      * A Web Script Test Request
588      */
589     public static class Request
590     {
591         private String method;
592         private String uri;
593         private Map<String, String> args;
594         private Map<String, String> headers;
595         private byte[] body;
596         private String encoding = "UTF-8";
597         private String contentType;
598         
599         public Request(Request req)
600         {
601             this.method = req.method;
602             this.uri= req.uri;
603             this.args = req.args;
604             this.headers = req.headers;
605             this.body = req.body;
606             this.encoding = req.encoding;
607             this.contentType = req.contentType;
608         }
609         
610         public Request(String method, String uri)
611         {
612             this.method = method;
613             this.uri = uri;
614         }
615         
616         public String getMethod()
617         {
618             return method;
619         }
620         
621         public String getUri()
622         {
623             return uri;
624         }
625         
626         public String getFullUri()
627         {
628             // calculate full uri
629             String fullUri = uri == null ? "" : uri;
630             if (args != null && args.size() > 0)
631             {
632                 char prefix = (uri.indexOf('?') == -1) ? '?' : '&';
633                 for (Map.Entry<String, String> arg : args.entrySet())
634                 {
635                     fullUri += prefix + arg.getKey() + "=" + (arg.getValue() == null ? "" : arg.getValue());
636                     prefix = '&';
637                 }
638             }
639             
640             return fullUri;
641         }
642         
643         public Request setArgs(Map<String, String> args)
644         {
645             this.args = args;
646             return this;
647         }
648         
649         public Map<String, String> getArgs()
650         {
651             return args;
652         }
653 
654         public Request setHeaders(Map<String, String> headers)
655         {
656             this.headers = headers;
657             return this;
658         }
659         
660         public Map<String, String> getHeaders()
661         {
662             return headers;
663         }
664         
665         public Request setBody(byte[] body)
666         {
667         	this.body = body;
668             return this;
669         }
670         
671         public byte[] getBody()
672         {
673             return body;
674         }
675         
676         public Request setEncoding(String encoding)
677         {
678             this.encoding = encoding;
679             return this;
680         }
681         
682         public String getEncoding()
683         {
684             return encoding;
685         }
686 
687         public Request setType(String contentType)
688         {
689             this.contentType = contentType;
690             return this;
691         }
692         
693         public String getType()
694         {
695             return contentType;
696         }
697     }
698     
699     /**
700      * Test GET Request
701      */
702     public static class GetRequest extends Request
703     {
704         public GetRequest(String uri)
705         {
706             super("get", uri);
707         }
708     }
709 
710     /**
711      * Test POST Request
712      */
713     public static class PostRequest extends Request
714     {
715         public PostRequest(String uri, String post, String contentType)
716             throws UnsupportedEncodingException 
717         {
718             super("post", uri);
719             setBody(getEncoding() == null ? post.getBytes() : post.getBytes(getEncoding()));
720             setType(contentType);
721         }
722 
723         public PostRequest(String uri, byte[] post, String contentType)
724         {
725             super("post", uri);
726             setBody(post);
727             setType(contentType);
728         }
729     }
730 
731     /**
732      * Test PUT Request
733      */
734     public static class PutRequest extends Request
735     {
736         public PutRequest(String uri, String put, String contentType)
737             throws UnsupportedEncodingException
738         {
739             super("put", uri);
740             setBody(getEncoding() == null ? put.getBytes() : put.getBytes(getEncoding()));
741             setType(contentType);
742         }
743         
744         public PutRequest(String uri, byte[] put, String contentType)
745         {
746             super("put", uri);
747             setBody(put);
748             setType(contentType);
749         }
750     }
751 
752     /**
753      * Test DELETE Request
754      */
755     public static class DeleteRequest extends Request
756     {
757         public DeleteRequest(String uri)
758         {
759             super("delete", uri);
760         }
761     }
762 
763     /**
764      * Test PATCH Request
765      */
766     public static class PatchRequest extends Request
767     {
768         public PatchRequest(String uri, String put, String contentType)
769             throws UnsupportedEncodingException
770         {
771             super("patch", uri);
772             setBody(getEncoding() == null ? put.getBytes() : put.getBytes(getEncoding()));
773             setType(contentType);
774         }
775         
776         public PatchRequest(String uri, byte[] put, String contentType)
777         {
778             super("patch", uri);
779             setBody(put);
780             setType(contentType);
781         }
782     }
783     
784     /**
785      * A Web Script Test Response
786      */
787     public interface Response
788     {
789         public byte[] getContentAsByteArray();
790         
791         public String getContentAsString()
792             throws UnsupportedEncodingException;
793         
794         public String getHeader(String name);
795         
796         public String getContentType();
797         
798         public int getContentLength();
799         
800         public int getStatus();
801     }
802     
803     /**
804      * Test Response wrapping a MockHttpServletResponse
805      */
806     public static class MockHttpServletResponseResponse
807         implements Response
808     {
809         private MockHttpServletResponse res;
810         
811         public MockHttpServletResponseResponse(MockHttpServletResponse res)
812         {
813             this.res = res;
814         }
815 
816         public byte[] getContentAsByteArray()
817         {
818             return res.getContentAsByteArray();
819         }
820 
821         public String getContentAsString()
822             throws UnsupportedEncodingException
823         {
824             return res.getContentAsString();
825         }
826 
827         public String getHeader(String name)
828         {
829             return (String)res.getHeader(name);
830         }
831         
832         public String getContentType()
833         {
834             return res.getContentType();
835         }
836 
837         public int getContentLength()
838         {
839             return res.getContentLength();
840         }
841 
842         public int getStatus()
843         {
844             return res.getStatus();
845         }
846     }
847 
848 }