adding all of botlist, initial add
[botlist.git] / openbotlist / src / org / spirit / spring / BotListRubyController.java
blob54de90aa75edae274fbb8b8e8f17fc9e23f73928
1 /*
2 *** Notice Update: 8/14/2007
3 *** Copyright 2007 Berlin Brown
4 *** Copyright 2006-2007 Newspiritcompany.com
5 ***
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.
9 ***
10 *** See LICENSE.BOTLIST for more information.
11 ***
12 *** The SOFTWARE PRODUCT and CODE are protected by copyright and
13 *** other intellectual property laws and treaties.
14 ***
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
18 *** implied.
20 package org.spirit.spring;
22 import java.io.BufferedInputStream;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileNotFoundException;
26 import java.io.IOException;
27 import java.util.Iterator;
28 import java.util.Map;
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;
51 /**
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";
64 /**
65 * User Link Data Access Object.
67 private BotListUserLinkDAO userLinkDAO = null;
69 /**
70 * Post Listing DAO.
72 private BotListPostListingDAO postListingDAO = null;
74 /**
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";
90 /**
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) == '/') {
133 beg = 1;
134 } else {
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('.');
142 if (dot >= 0) {
143 end = dot;
145 viewName = viewName.substring(beg, end);
146 return viewName;
149 public String getDefaultViewNameFromRequest(HttpServletRequest request) {
150 //log.info(CLASS_IDENTIFIER() + "getDefaultViewNameFromRequest() - From Request: " + request.getRequestURL());
151 // TODO: to be fixed
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();
200 } else {
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]);
220 try {
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());
225 } else {
226 return getModelAndView(result, getDefaultViewNameFromRequest(request));
228 } catch(BSFException bfe) {
229 bfe.printStackTrace();
230 log.error(bfe);
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);
258 } else {
259 log.error(CLASS_IDENTIFIER() + "-- ERR: Invalid Database Object Helper --");
261 if (this.getPostListingDao() != null) {
262 mManager.declareBean("daohelperlisting", getPostListingDao(), getPostListingDao().getClass());
263 } else {
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) {
281 initializeManager();
283 return mManager;
287 * Read an entire ruby script in from a file. Isn't there a better way to do this?
289 * @param filename
290 * @return
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);
304 return rubyCode;
308 * Get JSP Path Pos.
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);
337 } else {
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);
344 if (reloadFile) {
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 + "");
349 } else {
350 rubyCode = cacheObj.getRubyCodeData();
353 return rubyCode;
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.
366 * @param request
367 * @return
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;
375 try {
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());
380 if (lastPos == -1) {
381 lastPos = 0;
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);
394 } else {
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) {
401 log.error(fne);
402 System.err.println("*** WARN: Script not found ********* [" + request.getRequestURI() + "]");
403 } catch (IOException e) {
404 e.printStackTrace();
405 log.error(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) {
410 setManager(null);
411 bfe.printStackTrace();
412 log.error(bfe);
413 Throwable targetException = bfe.getTargetException();
414 throw new BSFException(bfe.getReason(), targetException.getMessage(), targetException);
416 return controller;
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;
434 try {
435 String baseFilePath = getServletContext().getRealPath(jspdir);
436 String filename = baseFilePath + path;
437 String rubyCode = "";
438 if (getCacheController().isEnableCaching()) {
439 rubyCode = executeCache(filename);
440 } else {
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) {
448 log.error(e);
449 System.err.println(e.getMessage());
450 } catch (BSFException bfe) {
451 setManager(null);
452 bfe.printStackTrace();
453 log.error(bfe);
454 Throwable targetException = bfe.getTargetException();
455 throw new BSFException(bfe.getReason(), targetException.getMessage(), targetException);
457 return controller;
461 * Invoke Ruby Controller Method.
463 * @param rubyController
464 * @param methodName
465 * @param args
466 * @return
467 * @throws BSFException
469 protected Object invokeRubyControllerMethod(Object rubyController, String methodName, Object[] args) throws BSFException {
470 if (rubyController == null) {
471 return null;
473 return getEngine().call(rubyController, methodName, args);
477 * Invoke the Ruby Controller Method.
479 * @param request
480 * @param methodName
481 * @param args
482 * @return
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();
497 Object model = null;
498 Object mapOrController = getRubyController(request);
500 if (mapOrController != null) {
501 if (Map.class.isAssignableFrom(mapOrController.getClass())) {
502 model = mapOrController;
503 } else {
504 try {
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());
511 log.error(be);
512 // Stop normal execution
513 // TODO: redirect to error page? have some kind of backout routine?
514 throw be;
515 } // End - try - catch bsfmanager exceptions
518 long curEndTime = System.currentTimeMillis();
519 long diff = curEndTime - scriptStartTime;
520 log.debug("formBackingObject(): processing=" + diff + " ms");
522 if (model == null) {
523 TreeMap locTreeMap = new TreeMap();
524 this.setRubyCommand(locTreeMap);
525 return locTreeMap;
527 this.setRubyCommand(model);
528 return 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() {
542 return mJspDir;
544 public void setJspDir(String jspDir) {
545 mJspDir = jspDir;
548 public Log getLogger() {
549 return log;
551 public void setLogger(Log logger) {
552 log = logger;
555 public void setManager(BSFManager manager) {
556 this.mManager = manager;
558 public JRubyEngine getEngine() {
559 return mEngine;
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() {
587 return mInitScript;
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) {
657 int res = -1;
658 if (this.getFileUploadUtil() != null) {
659 try {
660 res = this.fileUploadUtil.uploadFiles(request);
661 } catch (Exception e) {
662 log.error(e.getMessage());
664 } else {
665 log.error("ERR: File Upload Bean is NULL, should be set properly in the spring configuration");
667 return res;
671 * @return the rubyCommand
673 public Object getRubyCommand() {
674 return rubyCommand;
678 * @param rubyCommand the rubyCommand to set
680 public void setRubyCommand(Object rubyCommand) {
681 this.rubyCommand = rubyCommand;