Index: scriptlet/xml/XMLScriptlet.class.php =================================================================== --- scriptlet/xml/XMLScriptlet.class.php (revision 8451) +++ scriptlet/xml/XMLScriptlet.class.php (working copy) @@ -58,7 +58,7 @@ * @param string base default '' */ function __construct($base= '') { - $this->processor= &$this->_processor(); + $this->processor= &$this->processorInstance(); $this->processor->setBase($base); } @@ -68,10 +68,13 @@ * @access protected * @return &xml.XSLProcessor */ - function &_processor() { + function &processorInstance() { + + // Check for deprecated _processor() method + if (method_exists($this, '_processor')) return $this->_processor(); return new XSLProcessor(); } - + /** * Set our own response object * Index: scriptlet/xml/workflow/WorkflowXMLScriptlet.class.php =================================================================== --- scriptlet/xml/workflow/WorkflowXMLScriptlet.class.php (revision 0) +++ scriptlet/xml/workflow/WorkflowXMLScriptlet.class.php (revision 0) @@ -0,0 +1,196 @@ +package= rtrim($package, '.'); + } + + /** + * Create the router object. Returns a ClassRouter in this implementation + * which resembles the previously hardcoded behaviour. + * + * @access protected + * @param &scriptlet.xml.XMLScriptletRequest request + * @return &scriptlet.xml.workflow.Router + */ + function &routerFor(&$request) { + return new ClassRouter(); + } + + /** + * Retrieve context class + * + * @access protected + * @param &scriptlet.xml.XMLScriptletRequest request + * @return &lang.XPClass + * @throws lang.ClassNotFoundException + */ + function &getContextClass(&$request) { + return XPClass::forName($this->package.'.'.(ucfirst($request->getProduct()).'Context')); + } + + /** + * Decide whether a session is needed + * + * @access protected + * @param &scriptlet.xml.XMLScriptletRequest request + * @return bool + */ + function needsSession(&$request) { + return ($request->state && ( + $request->state->hasHandlers() || + $request->state->requiresAuthentication() + )); + } + + /** + * Decide whether a context is needed. Returns FALSE in this default + * implementation. + * + * @access protected + * @param &scriptlet.xml.XMLScriptletRequest request + * @return bool + */ + function wantsContext(&$request) { + return FALSE; + } + + /** + * Process workflow. Calls the state's setup() and process() + * methods in this order. May NOT be overwritten by subclasses. + * + * Return FALSE from this method to indicate no further + * processing is to be done + * + * @model final + * @access protected + * @param &scriptlet.xml.XMLScriptletRequest request + * @param &scriptlet.xml.XMLScriptletResponse response + * @return bool + */ + function processWorkflow(&$request, &$response) { + + // Context initialization + $context= NULL; + if ($this->wantsContext($request) && $request->hasSession()) { + + // Set up context. The context contains - so to say - the "autoglobals", + // in other words, the omnipresent data such as, for example, the user + try(); { + $class= &$this->getContextClass($request); + } if (catch('ClassNotFoundException', $e)) { + throw(new HttpScriptletException($e->getMessage())); + return FALSE; + } + + // Get context from session. If it is not available there, set up the + // context and store it to the session. + $cidx= $class->getName(); + if (!($context= &$request->session->getValue($cidx))) { + $context= &$class->newInstance(); + + try(); { + $context->setup($request); + } if (catch('IllegalStateException', $e)) { + throw(new HttpScriptletException($e->getMessage(), HTTP_INTERNAL_SERVER_ERROR)); + return FALSE; + } if (catch('IllegalArgumentException', $e)) { + throw(new HttpScriptletException($e->getMessage(), HTTP_NOT_ACCEPTABLE)); + return FALSE; + } if (catch('IllegalAccessException', $e)) { + throw(new HttpScriptletException($e->getMessage(), HTTP_FORBIDDEN)); + return FALSE; + } + $request->session->putValue($cidx, $context); + } + + // Run context's process() method. + try(); { + $context->process($request); + } if (catch('IllegalStateException', $e)) { + throw(new HttpSessionInvalidException($e->getMessage(), HTTP_BAD_REQUEST)); + return FALSE; + } if (catch('IllegalAccessException', $e)) { + throw(new HttpScriptletException($e->getMessage(), HTTP_FORBIDDEN)); + return FALSE; + } + + delete($class); + } + + // Routing + try(); { + $router= &$this->routerFor($request); + $route= &$router->route($this->package.'.state.', $request, $response, $context); + $route && $r= $route->dispatch($request, $response, $context); + } if (catch('ClassNotFoundException', $e)) { + throw(new HttpScriptletException($e->getMessage(), HTTP_METHOD_NOT_ALLOWED)); + return FALSE; + } if (catch('IllegalStateException', $e)) { + throw(new HttpScriptletException($e->getMessage(), HTTP_INTERNAL_SERVER_ERROR)); + return FALSE; + } if (catch('IllegalArgumentException', $e)) { + throw(new HttpScriptletException($e->getMessage(), HTTP_NOT_ACCEPTABLE)); + return FALSE; + } if (catch('IllegalAccessException', $e)) { + throw(new HttpScriptletException($e->getMessage(), HTTP_FORBIDDEN)); + return FALSE; + } + + // If dispatching returns FALSE, the context's insertStatus() method + // will not be called. This, for example, is useful when a state wants + // to send a redirect. + if (FALSE === $r) return FALSE; + + // If there is no context, we're finished + if (!$context) return; + + // Tell context to insert form elements. Then store it, if necessary + $context->insertStatus($response); + $context->getChanged() && $request->session->putValue($cidx, $context); + } + + /** + * Process request + * + * @access protected + * @param &scriptlet.xml.XMLScriptletRequest request + * @param &scriptlet.xml.XMLScriptletResponse response + */ + function processRequest(&$request, &$response) { + if (FALSE === $this->processWorkflow($request, $response)) { + + // The processWorkflow() method indicates no further processing + // is to be done. Pass result "up". + return FALSE; + } + + return parent::processRequest($request, $response); + } + } +?> Property changes on: scriptlet/xml/workflow/WorkflowXMLScriptlet.class.php ___________________________________________________________________ Name: svn:keywords + Id Index: scriptlet/xml/workflow/StaticRouter.class.php =================================================================== --- scriptlet/xml/workflow/StaticRouter.class.php (revision 0) +++ scriptlet/xml/workflow/StaticRouter.class.php (revision 0) @@ -0,0 +1,48 @@ + + * State name Class name + * ----------- ------------------------------ + * /static [package.]StaticState + * /news/view [package.]NewsState + * + * + * @see xp://scriptlet.xml.workflow.StateRoute + * @see xp://scriptlet.xml.workflow.Router + * @purpose Router implementation + */ + class StaticRouter extends Object { + + /** + * Route a request + * + * @access public + * @param string package + * @param &scriptlet.xml.XMLScriptletRequest + * @param &scriptlet.xml.XMLScriptletResponse + * @param &scriptlet.xml.workflow.Context + * @return &scriptlet.xml.workflow.Route + * @throws lang.ClassNotFoundException in case the state class cannot be found + */ + function &route($package, &$request, &$response, &$context) { + try(); { + $class= &XPClass::forName($package.ucfirst(strtok($request->getStateName(), '/')).'State'); + } if (catch('ClassNotFoundException', $e)) { + return throw($e); + } + + return new StateRoute(ref($class->newInstance())); + } + + } implements(__FILE__, 'scriptlet.xml.workflow.Router'); +?> Property changes on: scriptlet/xml/workflow/StaticRouter.class.php ___________________________________________________________________ Name: svn:keywords + Id Index: scriptlet/xml/workflow/Route.class.php =================================================================== --- scriptlet/xml/workflow/Route.class.php (revision 0) +++ scriptlet/xml/workflow/Route.class.php (revision 0) @@ -0,0 +1,26 @@ + Property changes on: scriptlet/xml/workflow/Route.class.php ___________________________________________________________________ Name: svn:keywords + Id Index: scriptlet/xml/workflow/MethodRoute.class.php =================================================================== --- scriptlet/xml/workflow/MethodRoute.class.php (revision 0) +++ scriptlet/xml/workflow/MethodRoute.class.php (revision 0) @@ -0,0 +1,51 @@ +state= &deref($state); + $this->method= &deref($method); + } + + /** + * Dispatch this route + * + * @access public + * @param &scriptlet.xml.XMLScriptletRequest + * @param &scriptlet.xml.XMLScriptletResponse + * @param &scriptlet.xml.workflow.Context + * @return mixed + */ + function dispatch(&$request, &$response, &$context) { + $request->state= &$this->state; + try(); { + $this->state->setup($request, $response, $context); + } if (catch('Throwable', $e)) { + return throw($e); + } + return $this->method->invoke($this->state, array(&$request, &$response, &$context)); + } + + } implements(__FILE__, 'scriptlet.xml.workflow.Route'); +?> Property changes on: scriptlet/xml/workflow/MethodRoute.class.php ___________________________________________________________________ Name: svn:keywords + Id Index: scriptlet/xml/workflow/StateRoute.class.php =================================================================== --- scriptlet/xml/workflow/StateRoute.class.php (revision 0) +++ scriptlet/xml/workflow/StateRoute.class.php (revision 0) @@ -0,0 +1,49 @@ +state= &deref($state); + } + + /** + * Dispatch this route + * + * @access public + * @param &scriptlet.xml.XMLScriptletRequest + * @param &scriptlet.xml.XMLScriptletResponse + * @param &scriptlet.xml.workflow.Context + * @return mixed + */ + function dispatch(&$request, &$response, &$context) { + $request->state= &$this->state; + try(); { + $this->state->setup($request, $response, $context); + } if (catch('Throwable', $e)) { + return throw($e); + } + return $this->state->process($request, $response, $context); + } + + } implements(__FILE__, 'scriptlet.xml.workflow.Route'); +?> Property changes on: scriptlet/xml/workflow/StateRoute.class.php ___________________________________________________________________ Name: svn:keywords + Id Index: scriptlet/xml/workflow/DelegatingRouter.class.php =================================================================== --- scriptlet/xml/workflow/DelegatingRouter.class.php (revision 0) +++ scriptlet/xml/workflow/DelegatingRouter.class.php (revision 0) @@ -0,0 +1,65 @@ + + * new DelegatingRouter(new ClassRouter(), array( + * 'static' => new StaticRouter(), + * 'news' => new MethodRouter() + * )); + * + * + * @see xp://scriptlet.xml.workflow.Router + * @purpose Router implementation + */ + class DelegatingRouter extends Object { + var + $rules= array(); + + /** + * Constructor. + * + * @access public + * @param &scriptlet.xml.workflow.Router default router + * @param array rules default array() + */ + function __construct(&$default, $rules= array()) { + $this->rules[0]= &deref($default); + foreach (array_keys($rules) as $fragment) { + $this->rules[$fragment]= &$rules[$fragment]; + } + } + + /** + * Route a request + * + * @access public + * @param string package + * @param &scriptlet.xml.XMLScriptletRequest + * @param &scriptlet.xml.XMLScriptletResponse + * @param &scriptlet.xml.workflow.Context + * @return &scriptlet.xml.workflow.Route + * @throws lang.ClassNotFoundException in case the state class cannot be found + */ + function &route($package, &$request, &$response, &$context) { + $stateName= $request->getStateName(); + foreach (array_filter(array_keys($this->rules)) as $pattern) { + if (0 == strncmp($pattern, $stateName, strlen($pattern))) { + return $this->rules[$pattern]->route($package, $request, $response, $context); + } + } + + // Apply default route + return $this->rules[0]->route($package, $request, $response, $context); + } + + } implements(__FILE__, 'scriptlet.xml.workflow.Router'); +?> Property changes on: scriptlet/xml/workflow/DelegatingRouter.class.php ___________________________________________________________________ Name: svn:keywords + Id Index: scriptlet/xml/workflow/ClassRouter.class.php =================================================================== --- scriptlet/xml/workflow/ClassRouter.class.php (revision 0) +++ scriptlet/xml/workflow/ClassRouter.class.php (revision 0) @@ -0,0 +1,48 @@ + + * State name Class name + * ----------- ------------------------------ + * /static [package.]StaticState + * /news/view [package.]ViewNewsState + * + * + * @see xp://scriptlet.xml.workflow.StateRoute + * @see xp://scriptlet.xml.workflow.Router + * @purpose Router implementation + */ + class ClassRouter extends Object { + + /** + * Route a request + * + * @access public + * @param string package + * @param &scriptlet.xml.XMLScriptletRequest + * @param &scriptlet.xml.XMLScriptletResponse + * @param &scriptlet.xml.workflow.Context + * @return &scriptlet.xml.workflow.Route + * @throws lang.ClassNotFoundException in case the state class cannot be found + */ + function &route($package, &$request, &$response, &$context) { + try(); { + $state= &XPClass::forName($package.implode('', array_map('ucfirst', array_reverse(explode('/', $request->getStateName())))).'State'); + } if (catch('ClassNotFoundException', $e)) { + return throw($e); + } + + return new StateRoute(ref($state->newInstance())); + } + + } implements(__FILE__, 'scriptlet.xml.workflow.Router'); +?> Property changes on: scriptlet/xml/workflow/ClassRouter.class.php ___________________________________________________________________ Name: svn:keywords + Id Index: scriptlet/xml/workflow/WorkflowScriptletRequest.class.php =================================================================== --- scriptlet/xml/workflow/WorkflowScriptletRequest.class.php (revision 8451) +++ scriptlet/xml/workflow/WorkflowScriptletRequest.class.php (working copy) @@ -9,6 +9,7 @@ /** * Wraps request * + * @deprecated RFC #0090: Use WorkflowXMLScriptlet instead of AbstractXMLScriptlet * @see xp://scriptlet.xml.XMLScriptletRequest * @purpose Scriptlet request wrapper */ Index: scriptlet/xml/workflow/AbstractXMLScriptlet.class.php =================================================================== --- scriptlet/xml/workflow/AbstractXMLScriptlet.class.php (revision 8451) +++ scriptlet/xml/workflow/AbstractXMLScriptlet.class.php (working copy) @@ -12,6 +12,7 @@ /** * Workflow model scriptlet implementation * + * @deprecated RFC #0090: Use WorkflowXMLScriptlet instead of AbstractXMLScriptlet * @purpose Base class */ class AbstractXMLScriptlet extends XMLScriptlet { Index: scriptlet/xml/workflow/Router.class.php =================================================================== --- scriptlet/xml/workflow/Router.class.php (revision 0) +++ scriptlet/xml/workflow/Router.class.php (revision 0) @@ -0,0 +1,29 @@ + Property changes on: scriptlet/xml/workflow/Router.class.php ___________________________________________________________________ Name: svn:keywords + Id Index: scriptlet/xml/workflow/MethodRouter.class.php =================================================================== --- scriptlet/xml/workflow/MethodRouter.class.php (revision 0) +++ scriptlet/xml/workflow/MethodRouter.class.php (revision 0) @@ -0,0 +1,61 @@ + + * State name Class name + * ----------- ------------------------------- + * /static [package.]StaticState::index() + * /news/view [package.]NewsState::view() + * + * + * @see xp://scriptlet.xml.workflow.MethodRoute + * @see xp://scriptlet.xml.workflow.Router + * @purpose Router implementation + */ + class MethodRouter extends Object { + + /** + * Route a request + * + * @access public + * @param string package + * @param &scriptlet.xml.XMLScriptletRequest + * @param &scriptlet.xml.XMLScriptletResponse + * @param &scriptlet.xml.workflow.Context + * @return &scriptlet.xml.workflow.Route + * @throws lang.ClassNotFoundException in case the state class cannot be found + * @throws lang.IllegalArgumentException in case the operation cannot be found + * @throws lang.IllegalAccessException in case the operation is not publicly accessible + */ + function &route($package, &$request, &$response, &$context) { + 1 == sscanf($request->getStateName(), '%[^/]/%s', $name, $method) && $method= 'index'; + try(); { + $class= &XPClass::forName($package.ucfirst($name).'State'); + } if (catch('ClassNotFoundException', $e)) { + return throw($e); + } + + // Check if method exists + if (!($m= &$class->getMethod($method))) { + return throw(new IllegalArgumentException($class->getName().' does not support a '.$method.' operation')); + } + + // Check if method is public + if (!($m->getModifiers() & MODIFIER_PUBLIC)) { + return throw(new IllegalAccessException($class->getName().'::'.$method.' is not a public operation')); + } + + return new MethodRoute(ref($class->newInstance()), ref($m)); + } + + } implements(__FILE__, 'scriptlet.xml.workflow.Router'); +?> Property changes on: scriptlet/xml/workflow/MethodRouter.class.php ___________________________________________________________________ Name: svn:keywords + Id Index: scriptlet/xml/XMLScriptletRequest.class.php =================================================================== --- scriptlet/xml/XMLScriptletRequest.class.php (revision 8451) +++ scriptlet/xml/XMLScriptletRequest.class.php (working copy) @@ -31,6 +31,7 @@ class XMLScriptletRequest extends HttpScriptletRequest { var $product = '', + $state = NULL, $stateName = '', $language = '', $page = ''; @@ -43,7 +44,7 @@ function initialize() { parent::initialize(); $this->product= $this->getEnvValue('PRODUCT'); - $this->stateName= $this->getEnvValue('STATE'); + $this->stateName= rtrim($this->getEnvValue('STATE'), '/'); $this->language= $this->getEnvValue('LANGUAGE'); $this->page= isset($_REQUEST['__page']) ? $_REQUEST['__page'] : 'home'; } Index: net/xp_framework/unittest/scriptlet/workflow/mock/MockResponse.class.php =================================================================== --- net/xp_framework/unittest/scriptlet/workflow/mock/MockResponse.class.php (revision 8451) +++ net/xp_framework/unittest/scriptlet/workflow/mock/MockResponse.class.php (working copy) @@ -1,17 +0,0 @@ - Index: net/xp_framework/unittest/scriptlet/workflow/mock/MockRequest.class.php =================================================================== --- net/xp_framework/unittest/scriptlet/workflow/mock/MockRequest.class.php (revision 8451) +++ net/xp_framework/unittest/scriptlet/workflow/mock/MockRequest.class.php (working copy) @@ -1,50 +0,0 @@ - params default array() - */ - function __construct($package, $functionality, $stateName, $params= array()) { - static $i= 0; - - parent::__construct($package); - - // Generate unique classname and put it into the environment - // That way, the classloader will already know this class in - // WorkflowScriptletRequest::initialize() and be able to load - // and instantiate it. - $stateName= 'Mock·'.($i++).$stateName; - $class= &$classloader->defineClass( - 'net.xp_framework.unittest.scriptlet.workflow.mock.state.'.$stateName.'State', - 'class '.$stateName.'State extends AbstractState '.$functionality - ); - putenv('STATE='.$stateName); - - // Set some defaults - putenv('PRODUCT=xp'); - putenv('LANGUAGE=en_US'); - } - } -?> Index: net/xp_framework/unittest/scriptlet/workflow/WorkflowApiTest.class.php =================================================================== --- net/xp_framework/unittest/scriptlet/workflow/WorkflowApiTest.class.php (revision 8451) +++ net/xp_framework/unittest/scriptlet/workflow/WorkflowApiTest.class.php (working copy) @@ -6,10 +6,8 @@ uses( 'util.profiling.unittest.TestCase', - 'scriptlet.xml.workflow.AbstractXMLScriptlet', - 'scriptlet.xml.workflow.AbstractState', - 'net.xp_framework.unittest.scriptlet.workflow.mock.MockRequest', - 'net.xp_framework.unittest.scriptlet.workflow.mock.MockResponse' + 'scriptlet.xml.workflow.WorkflowXMLScriptlet', + 'scriptlet.xml.workflow.AbstractState' ); /** @@ -26,7 +24,45 @@ * @access public */ function setUp() { - $this->scriptlet= &new AbstractXMLScriptlet(ClassLoader::getDefault()); + $this->scriptlet= &newinstance('scriptlet.xml.workflow.WorkflowXMLScriptlet', array(''), '{ + function &_processor() { + return newinstance("xml.IXSLProcessor", array(), \'{ + function getMessages() { } + function setSchemeHandler($cb) { } + function setBase($dir) { } + function getBase() { } + function setXSLFile($file) { } + function setXSLBuf() { } + function setXMLFile($file) { } + function setXMLBuf($xml) { } + function setParams() { } + function setParam() { } + function getParam($name) { } + function run() { } + function output() { } + }\'); + } + + function &routerFor(&$request) { + return newinstance("scriptlet.xml.workflow.Router", array(), \'{ + function &route($package, &$request, &$response, &$context) { + return new StateRoute(ref(newinstance("scriptlet.xml.workflow.AbstractState", array(), \\\'{ + var $called= array(); + + function setup(&$request, &$response, &$context) { + parent::setup($request, $response, $context); + $this->called["setup"]= TRUE; + } + + function process(&$request, &$response, &$context) { + parent::process($request, $response, $context); + $this->called["process"]= TRUE; + } + }\\\'))); + } + }\'); + } + }'); $this->scriptlet->init(); } @@ -48,7 +84,7 @@ */ function &process(&$request) { $request->initialize(); - $response= &new MockResponse(); + $response= &new XMLScriptletResponse(); $this->scriptlet->processWorkflow($request, $response); return $response; } @@ -60,20 +96,8 @@ */ #[@test] function setupAndProcessCalled() { - $request= &new MockRequest($this->scriptlet->classloader, '{ - var $called= array(); - - function setup(&$request, &$response, &$context) { - parent::setup($request, $response, $context); - $this->called["setup"]= TRUE; - } - - function process(&$request, &$response, &$context) { - parent::process($request, $response, $context); - $this->called["process"]= TRUE; - } - }'); - $this->process($request); + $request= &new XMLScriptletRequest(); + $response= &$this->process($request); $this->assertTrue($request->state->called['setup']); $this->assertTrue($request->state->called['process']); }