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        
126        String apiFile = ServletUtils.getRequiredParameter( configuration, api + "." + ApiConfiguration.Parameters.API_FILE );
127        String fullGeneratedApiFile = getServletContext().getRealPath(apiFile);    
128        String namespace = ServletUtils.getRequiredParameter( configuration, api + "." + ApiConfiguration.Parameters.NAMESPACE );    
129        String classNames = ServletUtils.getParameter( configuration, api + "." + ApiConfiguration.Parameters.CLASSES, "" );
130        List<Class<?>> classes = getClasses( classNames );
131        
132        logger.info( "Servlet configuration: " + api + ".apiFile=" + apiFile + " => Api file: " + fullGeneratedApiFile);
133        logger.info( "Servlet configuration: " + api + ".namespace=" + namespace);
134        logger.info( "Servlet configuration: " + api + ".classes=" + classNames);   
135        
136        ApiConfiguration apiConfiguration = new ApiConfiguration( api, fullGeneratedApiFile, namespace, classes );
137        this.apiConfigurations.add( apiConfiguration );
138            
139        return apiConfiguration;
140      }
141    
142      private static List<Class<?>> getClasses( String classes )  {
143        
144        List<Class<?>> result = new ArrayList<Class<?>>();
145        if( StringUtils.isEmpty(classes) ) {
146          return result;
147        }
148        List<String> classNames = StringUtils.getNonBlankValues( classes, VALUES_SEPARATOR );
149        
150        for( String className : classNames ) {
151          logger.trace( "Looking for class '" + className + "'");
152          try {
153            Class<?> cls = Class.forName( className );
154            result.add( cls );
155          }
156          catch( ClassNotFoundException ex ) {
157            logger.fatal( ex.getMessage(), ex );
158            ServletConfigurationException e = ServletConfigurationException.forClassNotFound(className, ex ); 
159            throw e;
160          }
161        }
162        
163        return result;
164      }
165      
166      private static RequestType getFromRequestContentType( HttpServletRequest request ) {
167        assert request != null;
168        
169        String contentType = request.getContentType();
170        String contentTypeLowercase = "";
171        if( contentType != null ) {
172          contentTypeLowercase = contentType.toLowerCase();
173        }
174        
175        String pathInfo = request.getPathInfo();
176        
177        if( !StringUtils.isEmpty(pathInfo) && pathInfo.startsWith( PollRequestProcessor.PATHINFO_POLL_PREFIX)) {
178          return RequestType.POLL;
179        }
180        else if( contentTypeLowercase.startsWith( "application/json")) {
181          return RequestType.JSON;
182        }
183        else if( contentTypeLowercase.startsWith("application/x-www-form-urlencoded") && request.getMethod().toLowerCase().equals("post")) {
184          return RequestType.FORM_SIMPLE_POST;
185        }
186        else if( ServletFileUpload.isMultipartContent(request)) {
187          return RequestType.FORM_UPLOAD_POST;
188        }
189        else {
190          ServletUtils.logTraceDetailedRequestInformation(request);
191          
192          RequestException ex = RequestException.forRequestFormatNotRecognized();
193          logger.error( ex.getMessage(), ex );
194          throw ex;
195        }
196      }
197    
198      @Override
199      public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
200        doPost(request, response);
201      }
202    
203      @Override
204      public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
205        RequestType type = getFromRequestContentType(request);
206        
207        response.setContentType("text/html"); // MUST be "text/html" for uploads to work!
208        
209        switch( type ) {
210          case FORM_SIMPLE_POST:
211            this.processor.processSimpleFormPostRequest( request.getReader(), response.getWriter() );
212            break;
213          case FORM_UPLOAD_POST:
214            processUploadFormPost(request, response);
215            break;
216          case JSON:
217            this.processor.processJsonRequest( request.getReader(), response.getWriter() );
218            break;
219          case POLL:
220            this.processor.processPollRequest( request.getReader(), response.getWriter(), request.getPathInfo() );
221            break;
222        }
223      }
224    
225      private void processUploadFormPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
226        try {
227          this.processor.processUploadFormPostRequest( getFileItems(request), response.getWriter() );
228        }
229        catch( FileUploadException e ) {
230          this.processor.handleFileUploadException( e );
231        }
232      }
233      
234      @SuppressWarnings("unchecked") // Unfortunately, parseRequest returns List, not List<FileItem>
235      private List<FileItem> getFileItems(HttpServletRequest request) throws FileUploadException {
236        return this.upload.parseRequest(request);
237      }
238    
239    }