001    /*
002     * Copyright © 2008, 2009 Pedro Agulló Soliveres.
003     * 
004     * This file is part of DirectJNgine.
005     *
006     * DirectJNgine is free software: you can redistribute it and/or modify
007     * it under the terms of the GNU General Public License as published by
008     * the Free Software Foundation, either version 3 of the License.
009     *
010     * DirectJNgine is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with DirectJNgine.  If not, see <http://www.gnu.org/licenses/>.
017     * 
018     * This software uses the ExtJs library (http://extjs.com), which is 
019     * distributed under the GPL v3 license (see http://extjs.com/license).
020     */
021    
022    package com.softwarementors.extjs.djn.servlet;
023    
024    import java.io.IOException;
025    import java.util.ArrayList;
026    import java.util.List;
027    
028    import javax.servlet.ServletConfig;
029    import javax.servlet.ServletException;
030    import javax.servlet.http.HttpServlet;
031    import javax.servlet.http.HttpServletRequest;
032    import javax.servlet.http.HttpServletResponse;
033    
034    import org.apache.commons.fileupload.FileItem;
035    import org.apache.commons.fileupload.FileUploadException;
036    import org.apache.commons.fileupload.servlet.ServletFileUpload;
037    import org.apache.log4j.Logger;
038    
039    import com.softwarementors.extjs.djn.ServletUtils;
040    import com.softwarementors.extjs.djn.StringUtils;
041    import com.softwarementors.extjs.djn.config.ApiConfiguration;
042    import com.softwarementors.extjs.djn.config.GlobalConfiguration;
043    import com.softwarementors.extjs.djn.config.Registry;
044    import com.softwarementors.extjs.djn.router.PollRequestProcessor;
045    import com.softwarementors.extjs.djn.router.RequestException;
046    import com.softwarementors.extjs.djn.router.RequestRouter;
047    import com.softwarementors.extjs.djn.router.RequestType;
048    import com.softwarementors.extjs.djn.router.UploadFormPostRequestProcessor;
049    
050    public class DirectJNgineServlet extends HttpServlet {
051    
052      private static final Logger logger = Logger.getLogger( DirectJNgineServlet.class);
053    
054      /*********************************************************  
055       * GlobalParameters and configuration
056       *********************************************************/
057      private static final String VALUES_SEPARATOR = ",";
058      
059      private Registry registry;
060      private List<String> apis;
061      private List<ApiConfiguration> apiConfigurations = new ArrayList<ApiConfiguration>();
062      private GlobalConfiguration globalConfiguration;
063      private RequestRouter processor;
064      private ServletFileUpload upload;
065      
066      
067      @Override
068      public void init(ServletConfig configuration) throws ServletException
069      {
070        assert configuration != null;
071        super.init(configuration);
072        
073        retrieveGlobalConfiguration(configuration);    
074        retrieveApiConfigurations(configuration);
075        
076        this.registry = new Registry( this.apiConfigurations);
077        try {
078          this.registry.updateJavascriptApiFiles(this.globalConfiguration);
079        }
080        catch( IOException ex ) {
081          throw new ServletException( "Unable to create DirectJNgine API files",  ex );
082        }
083        
084        this.upload = UploadFormPostRequestProcessor.createFileUploader();
085        this.processor = initializeRequestProcessor();
086      }
087      
088      private RequestRouter initializeRequestProcessor() {
089        return new RequestRouter( this.registry, this.globalConfiguration.getDebug() );    
090      }
091     
092      private void retrieveGlobalConfiguration(ServletConfig configuration) {
093        assert configuration != null;
094        
095        ServletUtils.checkRequiredParameters(configuration, GlobalConfiguration.Parameters.APIS, GlobalConfiguration.Parameters.PROVIDERS_URL);
096        
097        String debug = ServletUtils.getParameter( configuration, GlobalConfiguration.Parameters.DEBUG, "false");
098        boolean isDebug = debug.equalsIgnoreCase("true") || debug.equals("1");
099        String apisParameter = ServletUtils.getRequiredParameter(configuration, GlobalConfiguration.Parameters.APIS);
100        this.apis = StringUtils.getNonBlankValues(apisParameter, VALUES_SEPARATOR);
101        String providersUrl = ServletUtils.getRequiredParameter(configuration, GlobalConfiguration.Parameters.PROVIDERS_URL);
102        
103        logger.info( "Servlet configuration: apis=" + apisParameter);
104        logger.info( "Servlet configuration: debug=" + isDebug);
105        logger.info( "Servlet configuration: providersUrl=" + providersUrl);
106        
107        this.globalConfiguration = new GlobalConfiguration( providersUrl, isDebug);
108      }
109    
110      private void retrieveApiConfigurations(ServletConfig configuration) {
111        assert configuration != null;
112    
113        for( String api : this.apis) {
114          createApiConfiguration( configuration, api );
115        }
116      }
117    
118      private ApiConfiguration createApiConfiguration(ServletConfig configuration, String api) {
119        assert configuration != null;
120        assert !StringUtils.isEmpty(api);
121        
122        ServletUtils.checkRequiredParameters(configuration,
123            api + "." + ApiConfiguration.Parameters.API_FILE,
124            api + "." + ApiConfiguration.Parameters.NAMESPACE,
125            api + "." + ApiConfiguration.Parameters.CLASSES );
126        
127        String apiFile = ServletUtils.getRequiredParameter( configuration, api + "." + ApiConfiguration.Parameters.API_FILE );
128        String fullGeneratedApiFile = getServletContext().getRealPath(apiFile);    
129        String namespace = ServletUtils.getRequiredParameter( configuration, api + "." + ApiConfiguration.Parameters.NAMESPACE );    
130        String classNames = ServletUtils.getRequiredParameter( configuration, api + "." + ApiConfiguration.Parameters.CLASSES );
131        List<Class<?>> classes = getClasses( classNames );
132        
133        logger.info( "Servlet configuration: " + api + ".apiFile=" + apiFile + " => Api file: " + fullGeneratedApiFile);
134        logger.info( "Servlet configuration: " + api + ".namespace=" + namespace);
135        logger.info( "Servlet configuration: " + api + ".classes=" + classNames);   
136        
137        ApiConfiguration apiConfiguration = new ApiConfiguration( api, fullGeneratedApiFile, namespace, classes );
138        this.apiConfigurations.add( apiConfiguration );
139            
140        return apiConfiguration;
141      }
142    
143      private static List<Class<?>> getClasses( String classes )  {
144        assert !StringUtils.isEmpty(classes);
145        
146        List<String> classNames = StringUtils.getNonBlankValues( classes, VALUES_SEPARATOR );
147        List<Class<?>> result = new ArrayList<Class<?>>();
148        
149        for( String className : classNames ) {
150          logger.trace( "Looking for class '" + className + "'");
151          try {
152            Class<?> cls = Class.forName( className );
153            result.add( cls );
154          }
155          catch( ClassNotFoundException ex ) {
156            logger.fatal( ex.getMessage(), ex );
157            ServletConfigurationException e = ServletConfigurationException.forClassNotFound(className, ex ); 
158            throw e;
159          }
160        }
161        
162        return result;
163      }
164      
165      private static RequestType getFromRequestContentType( HttpServletRequest request ) {
166        assert request != null;
167        
168        String contentType = request.getContentType();
169        String contentTypeLowercase = "";
170        if( contentType != null ) {
171          contentTypeLowercase = contentType.toLowerCase();
172        }
173        
174        String pathInfo = request.getPathInfo();
175        
176        if( !StringUtils.isEmpty(pathInfo) && pathInfo.startsWith( PollRequestProcessor.PATHINFO_POLL_PREFIX)) {
177          return RequestType.POLL;
178        }
179        else if( contentTypeLowercase.startsWith( "application/json")) {
180          return RequestType.JSON;
181        }
182        else if( contentTypeLowercase.startsWith("application/x-www-form-urlencoded") && request.getMethod().toLowerCase().equals("post")) {
183          return RequestType.FORM_SIMPLE_POST;
184        }
185        else if( ServletFileUpload.isMultipartContent(request)) {
186          return RequestType.FORM_UPLOAD_POST;
187        }
188        else {
189          ServletUtils.logTraceDetailedRequestInformation(request);
190          
191          RequestException ex = RequestException.forRequestFormatNotRecognized();
192          logger.error( ex.getMessage(), ex );
193          throw ex;
194        }
195      }
196    
197      @Override
198      public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
199        doPost(request, response);
200      }
201    
202      @Override
203      public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
204        RequestType type = getFromRequestContentType(request);
205        
206        response.setContentType("text/html"); // MUST be "text/html" for uploads to work!
207        
208        switch( type ) {
209          case FORM_SIMPLE_POST:
210            this.processor.processSimpleFormPostRequest( request.getReader(), response.getWriter() );
211            break;
212          case FORM_UPLOAD_POST:
213            processUploadFormPost(request, response);
214            break;
215          case JSON:
216            this.processor.processJsonRequest( request.getReader(), response.getWriter() );
217            break;
218          case POLL:
219            this.processor.processPollRequest( request.getReader(), response.getWriter(), request.getPathInfo() );
220            break;
221        }
222      }
223    
224      private void processUploadFormPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
225        try {
226          this.processor.processUploadFormPostRequest( getFileItems(request), response.getWriter() );
227        }
228        catch( FileUploadException e ) {
229          this.processor.handleFileUploadException( e );
230        }
231      }
232      
233      @SuppressWarnings("unchecked") // Unfortunately, parseRequest returns List, not List<FileItem>
234      private List<FileItem> getFileItems(HttpServletRequest request) throws FileUploadException {
235        return this.upload.parseRequest(request);
236      }
237    
238    }