2 *** Notice Update: 8/14/2007
3 *** Copyright 2007 Berlin Brown
4 *** Copyright 2006-2007 Newspiritcompany.com
6 *** This SOURCE FILE is licensed to NEWSPIRITCOMPANY.COM. Unless
7 *** otherwise stated, use or distribution of this program
8 *** for commercial purpose is prohibited.
10 *** See LICENSE.BOTLIST for more information.
12 *** The SOFTWARE PRODUCT and CODE are protected by copyright and
13 *** other intellectual property laws and treaties.
15 *** Unless required by applicable law or agreed to in writing, software
16 *** distributed under the License is distributed on an "AS IS" BASIS,
17 *** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
20 package org
.spirit
.spring
;
22 import java
.io
.BufferedInputStream
;
24 import java
.io
.FileInputStream
;
25 import java
.io
.FileNotFoundException
;
26 import java
.io
.IOException
;
27 import java
.util
.Iterator
;
29 import java
.util
.TreeMap
;
31 import javax
.servlet
.http
.HttpServletRequest
;
32 import javax
.servlet
.http
.HttpServletResponse
;
34 import org
.apache
.bsf
.BSFException
;
35 import org
.apache
.bsf
.BSFManager
;
36 import org
.apache
.commons
.logging
.Log
;
37 import org
.apache
.commons
.logging
.LogFactory
;
38 import org
.jruby
.javasupport
.bsf
.JRubyEngine
;
39 import org
.spirit
.cache
.BotListCacheController
;
40 import org
.spirit
.cache
.BotListCacheEntity
;
41 import org
.spirit
.dao
.BotListPostListingDAO
;
42 import org
.spirit
.dao
.BotListUserLinkDAO
;
43 import org
.spirit
.form
.base
.BotListBaseForm
;
44 import org
.spirit
.util
.BotListCookieManager
;
45 import org
.spirit
.util
.BotListFileUploadType
;
46 import org
.spirit
.util
.io
.JRubyIOHelper
;
47 import org
.springframework
.context
.ApplicationContext
;
48 import org
.springframework
.validation
.BindException
;
49 import org
.springframework
.web
.servlet
.ModelAndView
;
52 * Spring Controller that interfaces between spring and jruby; reads the ruby script and invokes the
53 * correct model and view.
55 * @author Berlin Brown
57 public class BotListRubyController
58 extends BotListRubyDAOHandler
{
60 private Log log
= LogFactory
.getLog(getClass());
61 private static final String _CLASS_IDENTIFIER
= "BotListRubyController: ";
62 private static final String PROCESSING_TIME
= "processingtime";
65 * User Link Data Access Object.
67 private BotListUserLinkDAO userLinkDAO
= null;
72 private BotListPostListingDAO postListingDAO
= null;
75 * Ruby and BSF Managers.
77 private BSFManager mManager
;
78 private JRubyEngine mEngine
;
80 // configuration parametes and defaults
81 private String mScriptEngineClass
= "org.jruby.javasupport.bsf.JRubyEngine";
83 private String mJspDir
= "/WEB-INF/jsps";
84 private String mScriptEngineName
= "ruby";
85 private String mScriptExtension
= "rb";
86 private String mInitScript
= "/WEB-INF/jsps/INIT.rb";
88 private String springServletContext
= "spring";
91 * Record keeping, when the script starts and ends.
93 private long scriptStartTime
= 0;
94 private long scriptEndTime
= 0;
96 private BotListCacheController cacheController
;
98 public static final String DECLARE_BEAN_CONTEXT
= "context";
99 public static final String DECLARE_BEAN_REQUEST
= "request";
100 public static final String DECLARE_BEAN_RESPONSE
= "response";
101 public static final String DECLARE_BEAN_CONTROLLER
= "controller";
104 * Last command object instance.
106 private Object rubyCommand
;
109 * File Upload Properties and Processor.
111 private BotListFileUploadType fileUploadUtil
;
114 * Default Constructor.
116 BotListRubyController() {
117 this.setBindOnNewForm(true);
120 private static String
CLASS_IDENTIFIER() {
121 return _CLASS_IDENTIFIER
;
125 * This function is quite similar to the Jobster internal PathToViewController.
126 * Duplicated here so that the RAD module can be self contained.
128 public String
getViewNameFromServletPath(String servletPath
, String uri
, String contextPath
) {
130 String viewName
= servletPath
;
131 int beg
= 0, end
= viewName
.length();
132 if (end
> 0 && viewName
.charAt(0) == '/') {
135 // We are now assuming incoming servletPath is actually a full URL
136 // beg = servletPath.lastIndexOf('/');
137 if (getJspPathPos(uri
, contextPath
) != -1) {
138 beg
= getJspPathPos(uri
, contextPath
);
141 int dot
= viewName
.lastIndexOf('.');
145 viewName
= viewName
.substring(beg
, end
);
149 public String
getDefaultViewNameFromRequest(HttpServletRequest request
) {
150 //log.info(CLASS_IDENTIFIER() + "getDefaultViewNameFromRequest() - From Request: " + request.getRequestURL());
152 return getViewNameFromServletPath(request
.getRequestURI().substring(1),
153 request
.getRequestURI(), request
.getContextPath());
156 protected ModelAndView
getModelAndView(Object rubyResult
, String defaultView
) {
157 String viewName
= null;
158 if (Map
.class.isAssignableFrom(rubyResult
.getClass())) {
159 Map map
= (Map
) rubyResult
;
160 // look for an embedded view name in the model
161 viewName
= (String
) map
.get("viewName");
163 } else if (BotListBaseForm
.class.isAssignableFrom(rubyResult
.getClass())) {
165 // Or use the Base Class Form to get the view name.
166 BotListBaseForm form
= (BotListBaseForm
) rubyResult
;
167 if (form
.getViewName() != null) {
168 viewName
= form
.getViewName();
171 if (viewName
== null) {
172 viewName
= defaultView
;
174 TreeMap result
= new TreeMap();
175 result
.put(getCommandName(), rubyResult
);
177 // keep record of processing time
178 scriptEndTime
= System
.currentTimeMillis();
179 long diff
= scriptEndTime
- scriptStartTime
;
180 double diffS
= diff
/ 1000.0d
;
181 result
.put(PROCESSING_TIME
, "" + diffS
);
182 return new ModelAndView(viewName
, result
);
186 * Print Request Information
188 private void printRequestInfo(HttpServletRequest request
) {
189 //log.info(CLASS_IDENTIFIER() + " showForm() uri=" + request.getRequestURI());
193 * @see org.springframework.web.servlet.mvc.AbstractFormController#showForm(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.springframework.validation.BindException)
195 protected ModelAndView
showForm(HttpServletRequest request
, HttpServletResponse response
, BindException errors
) throws Exception
{
196 printRequestInfo(request
);
197 Object command
= null;
198 if (this.getRubyCommand() != null) {
199 command
= this.getRubyCommand();
201 command
= this.getCommand(request
);
203 return getModelAndView(command
, getDefaultViewNameFromRequest(request
));
207 * @see org.springframework.web.servlet.mvc.AbstractFormController#processFormSubmission(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, org.springframework.validation.BindException)
209 protected ModelAndView
processFormSubmission(HttpServletRequest request
, HttpServletResponse response
, Object command
, BindException errors
) throws Exception
{
210 // The command could be an instance of a map
211 // or one of the Form beans
212 if (command
instanceof Map
) {
213 Map map
= (java
.util
.Map
) command
;
214 for (Iterator iter
= request
.getParameterMap().entrySet().iterator(); iter
.hasNext();) {
215 Map
.Entry e
= (Map
.Entry
) iter
.next();
216 Object
[] values
= ((Object
[]) e
.getValue());
217 map
.put(e
.getKey(), values
[0]);
221 Object result
= invokeRubyControllerMethod(request
, "onSubmit",
222 new Object
[] { request
, response
, command
, errors
});
223 if (errors
.getErrorCount() > 0) {
224 return new ModelAndView(getDefaultViewNameFromRequest(request
), errors
.getModel());
226 return getModelAndView(result
, getDefaultViewNameFromRequest(request
));
228 } catch(BSFException bfe
) {
229 bfe
.printStackTrace();
231 Throwable targetException
= bfe
.getTargetException();
232 throw new BSFException(bfe
.getReason(), targetException
.getMessage(), targetException
);
237 * Get the botlist cache controller, get a new instance if not existing.
239 private BotListCacheController
getCacheController() {
240 if (this.cacheController
== null) {
241 this.cacheController
= new BotListCacheController();
242 this.cacheController
.setContext(this.getApplicationContext());
244 return this.cacheController
;
248 * Establish bean objects for Ruby environment.
250 * @throws BSFException
252 protected final void initializeManagerBeans() throws BSFException
{
253 getCacheController();
254 // Define the hibernate database objects for later use
255 mManager
.declareBean(DECLARE_BEAN_CONTEXT
, getApplicationContext(), ApplicationContext
.class);
256 if (this.getUserLinkDao() != null) {
257 mManager
.declareBean("daohelper", this.getUserLinkDao(), BotListUserLinkDAO
.class);
259 log
.error(CLASS_IDENTIFIER() + "-- ERR: Invalid Database Object Helper --");
261 if (this.getPostListingDao() != null) {
262 mManager
.declareBean("daohelperlisting", getPostListingDao(), getPostListingDao().getClass());
264 log
.error(CLASS_IDENTIFIER() + "-- ERR: Invalid Database Object Helper --");
267 // Set this object for DAO access
268 mManager
.declareBean(DECLARE_BEAN_CONTROLLER
, this, BotListRubyDAOHandler
.class);
271 protected void initializeManager() throws BSFException
{
272 BSFManager
.registerScriptingEngine(getScriptEngineName(), getScriptEngineClass(), new String
[] { getScriptExtension()});
273 mManager
= new BSFManager();
274 setEngine((JRubyEngine
) (mManager
.loadScriptingEngine(getScriptEngineName())));
275 initializeManagerBeans();
276 onInitializeManager();
279 public BSFManager
getManager() throws BSFException
{
280 if (mManager
== null) {
287 * Read an entire ruby script in from a file. Isn't there a better way to do this?
291 * @throws IOException
293 private String
readRubyScript(String filename
) throws IOException
{
295 FileInputStream fis
= null;
296 BufferedInputStream bis
= null;
298 fis
= new FileInputStream(filename
);
299 bis
= new BufferedInputStream(fis
);
300 String rubyCode
= JRubyIOHelper
.inputStreamToString(bis
);
302 JRubyIOHelper
.close(bis
);
303 JRubyIOHelper
.close(fis
);
309 * i.e. extract 'mypath/file.html' from '/webapp/b/mypath/file.html'
311 private int getJspPathPos(String uri
, String contextPath
) {
313 // Example, /webapp/spring
314 String target
= contextPath
+ "/" + this.getSpringServletContext();
315 int len
= target
.length();
316 return uri
.startsWith(target
) ? len
: -1;
320 * Execute the cache management for this particule ruby code.
321 * 1. If CACHE is empty, create new cache object.
322 * 2. If CACHE node is available, check node last modified vs file last modified
323 * 2a. If reloaded needed, create new cache object.
324 * 2b. Otherwise, use existing CACHE node.
326 public String
executeCache(String filename
) throws IOException
{
327 String rubyCode
= "";
328 if (this.cacheController
== null) {
329 this.getCacheController();
331 BotListCacheEntity cacheObj
= (BotListCacheEntity
) this.cacheController
.getManager().getCacheStore().get(filename
);
332 if (cacheObj
== null) {
333 rubyCode
= readRubyScript(filename
);
334 cacheObj
= BotListCacheController
.createCacheEntity(filename
, rubyCode
);
335 this.cacheController
.getManager().getCacheStore().put(filename
, cacheObj
);
336 log
.info(CLASS_IDENTIFIER() + "cache-manager: creating new cache entity=" + cacheObj
);
338 // Determine if we need to reload the cache file, otherwise, use existing.
339 File rFile
= new File(filename
);
340 long lastModified
= rFile
.lastModified();
341 long cacheLastModified
= cacheObj
.getLastModified().getTime();
342 long cacheModCheck
= ((lastModified
- cacheLastModified
) - (BotListCacheController
.CACHE_FREE_TIME
* 1000));
343 boolean reloadFile
= (cacheModCheck
> 0);
345 rubyCode
= readRubyScript(filename
);
346 cacheObj
= BotListCacheController
.createCacheEntity(filename
, rubyCode
);
347 this.cacheController
.getManager().getCacheStore().put(filename
, cacheObj
);
348 log
.info(CLASS_IDENTIFIER() + "cache-manager: reloading cache modifed-diff=" + cacheModCheck
+ " entity=" + cacheObj
+ "");
350 rubyCode
= cacheObj
.getRubyCodeData();
357 * Call system utilities before loading the ruby objects.
359 private void rubySystemInit(HttpServletRequest request
) {
360 // If user cooke is available, auto login.
361 BotListCookieManager
.systemGetUserCookieParams(request
, this.getCoreUsersDao(), this.getProfileSettingsDao());
365 * Get Ruby Controller.
368 * @throws BSFException
370 public Object
getRubyController(HttpServletRequest request
) throws BSFException
{
371 BSFManager manager
= getManager();
372 manager
.declareBean(DECLARE_BEAN_REQUEST
, request
, HttpServletRequest
.class);
373 final String CACHED_RUBY_CONTROLLER_ATTRIBUTE
= "__RUBYCONTROLLER";
374 Object controller
= null;
376 // Currently, getServletPath() is returning an incorrect value
377 // Use the full URL instead to get the correct servletPath
378 String fullURI
= request
.getRequestURI().toString();
379 int lastPos
= getJspPathPos(request
.getRequestURI(), request
.getContextPath());
383 String servletPath
= "/" + fullURI
.substring(lastPos
+ 1);
384 String baseFilePath
= getServletContext().getRealPath(getJspDir());
385 String filename
= baseFilePath
+ servletPath
.replaceAll("\\.[a-zA-Z]+$", "." + getScriptExtension());
386 String rubyCode
= "";
388 // System initialization.
389 rubySystemInit(request
);
391 // Simple cache management.
392 if (getCacheController().isEnableCaching()) {
393 rubyCode
= executeCache(filename
);
395 rubyCode
= readRubyScript(filename
);
397 controller
= manager
.eval(getScriptEngineName(), "(java)", 1, 1, rubyCode
);
398 request
.setAttribute("__manager", manager
);
399 request
.setAttribute(CACHED_RUBY_CONTROLLER_ATTRIBUTE
, controller
);
400 } catch (FileNotFoundException fne
) {
402 System
.err
.println("*** WARN: Script not found ********* [" + request
.getRequestURI() + "]");
403 } catch (IOException e
) {
406 // we don't care if the controller file doesn't exist-- we don't require one.
407 System
.err
.println("*** IO Error while reading script ********* [" + request
.getRequestURI() + "]");
408 System
.err
.println(e
.getMessage());
409 } catch (BSFException bfe
) {
411 bfe
.printStackTrace();
413 Throwable targetException
= bfe
.getTargetException();
414 throw new BSFException(bfe
.getReason(), targetException
.getMessage(), targetException
);
420 * Load the ruby script based from a servlet.
421 * The path will correspond with the actual ruby script, eg:
423 * "/chart/daily_chart.rb"
425 * @throws BSFException
427 public Object
getRubyServletController(String path
, String jspdir
, HttpServletRequest request
, HttpServletResponse response
)
428 throws BSFException
{
429 BSFManager manager
= getManager();
430 manager
.declareBean(DECLARE_BEAN_REQUEST
, request
, HttpServletRequest
.class);
431 manager
.declareBean(DECLARE_BEAN_RESPONSE
, response
, HttpServletResponse
.class);
432 final String CACHED_RUBY_CONTROLLER_ATTRIBUTE
= "__RUBYCONTROLLER";
433 Object controller
= null;
435 String baseFilePath
= getServletContext().getRealPath(jspdir
);
436 String filename
= baseFilePath
+ path
;
437 String rubyCode
= "";
438 if (getCacheController().isEnableCaching()) {
439 rubyCode
= executeCache(filename
);
441 rubyCode
= readRubyScript(filename
);
443 controller
= manager
.eval(getScriptEngineName(), "(java)", 1, 1, rubyCode
);
444 request
.setAttribute("__manager", manager
);
445 request
.setAttribute(CACHED_RUBY_CONTROLLER_ATTRIBUTE
, controller
);
447 } catch (IOException e
) {
449 System
.err
.println(e
.getMessage());
450 } catch (BSFException bfe
) {
452 bfe
.printStackTrace();
454 Throwable targetException
= bfe
.getTargetException();
455 throw new BSFException(bfe
.getReason(), targetException
.getMessage(), targetException
);
461 * Invoke Ruby Controller Method.
463 * @param rubyController
467 * @throws BSFException
469 protected Object
invokeRubyControllerMethod(Object rubyController
, String methodName
, Object
[] args
) throws BSFException
{
470 if (rubyController
== null) {
473 return getEngine().call(rubyController
, methodName
, args
);
477 * Invoke the Ruby Controller Method.
483 * @throws BSFException
485 protected Object
invokeRubyControllerMethod(HttpServletRequest request
, String methodName
, Object
[] args
) throws BSFException
{
486 return invokeRubyControllerMethod(getRubyController(request
), methodName
, args
);
490 * Form Backing Object.
491 * You must create the command without commandClass being set - either
492 * set commandClass or (in a form controller) override formBackingObject.
494 protected Object
formBackingObject(HttpServletRequest request
) throws Exception
{
496 this.scriptStartTime
= System
.currentTimeMillis();
498 Object mapOrController
= getRubyController(request
);
500 if (mapOrController
!= null) {
501 if (Map
.class.isAssignableFrom(mapOrController
.getClass())) {
502 model
= mapOrController
;
505 // Script errors and other BSF type errors are normally thrown
506 // here. Log the error and print stacktrace
507 model
= invokeRubyControllerMethod(mapOrController
, "getModel", new Object
[] { request
});
508 } catch(BSFException be
) {
509 be
.printStackTrace();
510 log
.error("Error: executing formBackingObject() script 'getModel', url=" + request
.getRequestURL());
512 // Stop normal execution
513 // TODO: redirect to error page? have some kind of backout routine?
515 } // End - try - catch bsfmanager exceptions
518 long curEndTime
= System
.currentTimeMillis();
519 long diff
= curEndTime
- scriptStartTime
;
520 log
.debug("formBackingObject(): processing=" + diff
+ " ms");
523 TreeMap locTreeMap
= new TreeMap();
524 this.setRubyCommand(locTreeMap
);
527 this.setRubyCommand(model
);
531 /**************************************************************************
532 * Setter and Getter Utility Methods
533 **************************************************************************/
534 protected void onInitializeManager() {
537 private void setEngine(JRubyEngine jRubyEngine
) {
538 mEngine
= jRubyEngine
;
541 public String
getJspDir() {
544 public void setJspDir(String jspDir
) {
548 public Log
getLogger() {
551 public void setLogger(Log logger
) {
555 public void setManager(BSFManager manager
) {
556 this.mManager
= manager
;
558 public JRubyEngine
getEngine() {
562 public String
getScriptEngineClass() {
563 return mScriptEngineClass
;
566 public void setScriptEngineClass(String scriptEngineClass
) {
567 mScriptEngineClass
= scriptEngineClass
;
570 public String
getScriptEngineName() {
571 return mScriptEngineName
;
574 public void setScriptEngineName(String scriptEngineName
) {
575 mScriptEngineName
= scriptEngineName
;
578 public String
getScriptExtension() {
579 return mScriptExtension
;
582 public void setScriptExtension(String scriptExtension
) {
583 mScriptExtension
= scriptExtension
;
586 public String
getInitScript() {
590 public void setInitScript(String initScript
) {
591 mInitScript
= initScript
;
594 /******************************************************
596 * Set the Data Access Object
598 ******************************************************/
599 public void setUserLinkDao(BotListUserLinkDAO dao
) {
600 this.userLinkDAO
= dao
;
603 public BotListUserLinkDAO
getUserLinkDao() {
604 return this.userLinkDAO
;
608 * @return the postListingDAO
610 public BotListPostListingDAO
getPostListingDao() {
611 return postListingDAO
;
615 * @param postListingDAO the postListingDAO to set
617 public void setPostListingDao(BotListPostListingDAO postListingDAO
) {
618 this.postListingDAO
= postListingDAO
;
622 * @return the springServletContext
624 public String
getSpringServletContext() {
625 return springServletContext
;
629 * @param springServletContext the springServletContext to set
631 public void setSpringServletContext(String springServletContext
) {
632 this.springServletContext
= springServletContext
;
635 /******************************************************
637 * Set the File Upload Properties
639 ******************************************************/
642 * @return the fileUploadUtil
644 public BotListFileUploadType
getFileUploadUtil() {
645 return fileUploadUtil
;
649 * @param fileUploadUtil the fileUploadUtil to set
651 public void setFileUploadUtil(BotListFileUploadType fileUploadUtil
) {
652 this.fileUploadUtil
= fileUploadUtil
;
655 public int parseFileUploadRequest(HttpServletRequest request
) {
658 if (this.getFileUploadUtil() != null) {
660 res
= this.fileUploadUtil
.uploadFiles(request
);
661 } catch (Exception e
) {
662 log
.error(e
.getMessage());
665 log
.error("ERR: File Upload Bean is NULL, should be set properly in the spring configuration");
671 * @return the rubyCommand
673 public Object
getRubyCommand() {
678 * @param rubyCommand the rubyCommand to set
680 public void setRubyCommand(Object rubyCommand
) {
681 this.rubyCommand
= rubyCommand
;