Merge branch '2016.11' into 2016.11-develop
[tine20] / tine20 / Tinebase / ActionQueue.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  ActionQueue
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Cornelius Weiss <c.weiss@metaways.de>
9  * @copyright   Copyright (c) 2009-2013 Metaways Infosystems GmbH (http://www.metaways.de)
10  */
11
12 /**
13  * Action Queue
14  * 
15  * Method queue for deferred/async execution of Tine 2.0 application actions as defined 
16  * in the application controllers 
17  *
18  * @package     Tinebase
19  * @subpackage  ActionQueue
20  *
21  * @method int getQueueSize()
22  * @method int waitForJob()
23  * @method string receive(integer $jobId)
24  * @method void delete(integer $jobId)
25  *
26  */
27  class Tinebase_ActionQueue implements Tinebase_Controller_Interface
28  {
29      const BACKEND_DIRECT = 'Direct';
30      const BACKEND_REDIS  = 'Redis';
31      
32      /**
33       * holds queue instance
34       * 
35       * @var Zend_Queue
36       */
37      protected $_queue = NULL;
38      
39     /**
40      * holds the instance of the singleton
41      *
42      * @var Tinebase_ActionQueue
43      */
44     private static $_instance = NULL;
45
46     /**
47      * don't clone. Use the singleton.
48      *
49      */
50     private function __clone() 
51     {
52     }
53     
54     /**
55      * the singleton pattern
56      *
57      * @return Tinebase_ActionQueue
58      */
59     public static function getInstance() 
60     {
61         if (self::$_instance === NULL) {
62             self::$_instance = new Tinebase_ActionQueue();
63         }
64         
65         return self::$_instance;
66     }
67     
68     /**
69      * destroy instance of this class
70      */
71     public static function destroyInstance()
72     {
73         self::$_instance = NULL;
74     }
75     
76     /**
77      * constructor
78      */
79     private function __construct()
80     {
81         $options = null;
82         $backend = self::BACKEND_DIRECT;
83         $config = Tinebase_Core::getConfig()->{Tinebase_Config::ACTIONQUEUE};
84
85         /** @noinspection PhpUndefinedFieldInspection */
86         if ($config && isset($config->{Tinebase_Config::ACTIONQUEUE_BACKEND}) && $config->{Tinebase_Config::ACTIONQUEUE_ACTIVE}) {
87             /** @noinspection PhpUndefinedFieldInspection */
88             $options = $config->toArray();
89             
90             $backend = (isset($options[Tinebase_Config::ACTIONQUEUE_BACKEND]) || array_key_exists(Tinebase_Config::ACTIONQUEUE_BACKEND, $options)) ? ucfirst(strtolower($options[Tinebase_Config::ACTIONQUEUE_BACKEND])) : $backend;
91             unset($options[Tinebase_Config::ACTIONQUEUE_BACKEND]);
92             unset($options[Tinebase_Config::ACTIONQUEUE_ACTIVE]);
93         }
94         
95         $className = 'Tinebase_ActionQueue_Backend_' . $backend;
96         
97         if (!class_exists($className)) {
98             if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(
99                 __METHOD__ . '::' . __LINE__ . " Queue class name {$className} not found. Falling back to direct execution.");
100             
101             $className = 'Tinebase_ActionQueue_Backend_Direct';
102         }
103     
104         $this->_queue = new $className($options); 
105
106         if (! $this->_queue instanceof Tinebase_ActionQueue_Backend_Interface) {
107             throw new Tinebase_Exception_UnexpectedValue('backend does not implement Tinebase_ActionQueue_Backend_Interface');
108         }
109     }
110
111      /**
112       * execute action defined in queue message
113       *
114       * @param  array $message action
115       * @return mixed
116       * @throws Tinebase_Exception_AccessDenied
117       * @throws Tinebase_Exception_NotFound
118       */
119     public function executeAction($message)
120     {
121         if (! is_array($message) || ! (isset($message['action']) || array_key_exists('action', $message)) || strpos($message['action'], '.') === FALSE) {
122             throw new Tinebase_Exception_NotFound('Could not execute action, invalid message/action param');
123         }
124
125         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(
126             __LINE__ . '::' . __METHOD__ . " executing action: '{$message['action']}'");
127         
128         list($appName, $actionName) = explode('.', $message['action']);
129         $controller = Tinebase_Core::getApplicationInstance($appName);
130     
131         if (! method_exists($controller, $actionName)) {
132             throw new Tinebase_Exception_NotFound('Could not execute action, requested action does not exist');
133         }
134         
135         return call_user_func_array(array($controller, $actionName), $message['params']);
136     }
137     
138     /**
139      * check if the backend is async
140      *  
141      * @return boolean true if queue backend is async
142      */
143     public function hasAsyncBackend()
144     {
145         return ! $this->_queue instanceof Tinebase_ActionQueue_Backend_Direct;
146     }
147     
148     /**
149      * process all jobs in queue
150      */
151     public function processQueue()
152     {
153         // loop over all jobs
154         while($jobId = Tinebase_ActionQueue::getInstance()->waitForJob()) {
155             $job = $this->receive($jobId);
156             
157             $this->executeAction($job);
158             
159             $this->delete($jobId);
160         }
161     }
162
163      /** @noinspection PhpDocSignatureInspection */
164      /**
165      * queues an action
166      *
167      * @param string $_action
168      * @param mixed  $_arg1
169      * @param mixed  $_arg2
170      * ...
171      * 
172      * @return string the job id
173      */
174     public function queueAction()
175     {
176         $params = func_get_args();
177         $action = array_shift($params);
178         $user = Tinebase_Core::getUser();
179         if (! is_object($user)) {
180             if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(
181                 __METHOD__ . '::' . __LINE__ . " Not Queueing action: '{$action}' because no valid user object found");
182             return null;
183         }
184
185         $message = array(
186             'action'     => $action,
187             'account_id' => $user->getId(),
188             'params'     => $params
189         );
190         
191         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(
192             __METHOD__ . '::' . __LINE__ . " Queueing action: '{$action}'");
193         
194         return $this->_queue->send($message);
195     }
196     
197     /**
198      * resume processing of events
199      */
200     public function resumeEvents()
201     {
202     }
203     
204     /**
205      * suspend processing of event
206      */
207     public function suspendEvents()
208     {
209     }
210
211     /**
212      * call function of queue backend
213      * 
214      * @param  string $name
215      * @param  array  $arguments
216      * @return mixed
217      */
218     public function __call($name, $arguments)
219     {
220         return call_user_func_array(array($this->_queue, $name), $arguments);
221     }
222
223      /**
224       * returns the class name of the used queue implementation
225       *
226       * @return string
227       */
228     public function getBackendType()
229     {
230         return get_class($this->_queue);
231     }
232 }