source: Dev/branches/rest-dojo-ui/server/classes/Model.php @ 303

Last change on this file since 303 was 303, checked in by hendrikvanantwerpen, 13 years ago

[Server] Refactored model classes with some meta-programming. Specific classes only define their fields and inherit from class RdfObject?. Changes to handle the new model objects correctly.
[Client] Added rft/store module for uniform resource access. Removed dependencies on 'uid' field name. Added support for references without loading full object nor exposing uri.
[Client] Added reset() to QuestionWidget?.
[RDFAPI] Fixed PHP warning.

File size: 16.1 KB
Line 
1<?php
2
3require_once RDFAPI_INCLUDE_DIR . 'RDFAPI.php';
4
5interface RdfResource {
6
7    function getUri();
8
9    function getUid();
10
11    function getType();
12}
13
14abstract class RdfObject implements RdfResource, IteratorAggregate {
15    // namespace and uri functions
16
17    const NS = 'http://tbm.tudelft.nl/researchtool/';
18    const RESOURCES_NS = 'http://tbm.tudelft.nl/researchtool/resources/';
19    const PREDICATES_NS = 'http://tbm.tudelft.nl/researchtool/predicates/';
20
21    public static function isResourceUri($str) {
22        $ptrn = '/^' . preg_quote(self::RESOURCES_NS, '/') . '\w+\/\w+$/';
23        return (bool) preg_match($ptrn, $str);
24    }
25
26    public static function resourceUriToTypeAndUid($uri) {
27        if (!self::isResourceUri($uri)) {
28            throw new Exception("Given URI $uri is not a valid resource.");
29        }
30        $ptrn = '/^' . preg_quote(self::RESOURCES_NS, '/') . '(\w+)\/(\w+)$/';
31        $matches = array();
32        preg_match_all($ptrn, $uri, $matches);
33        return array('type' => $matches[1][0], 'uid' => $matches[2][0]);
34    }
35
36    public static function typeAndUidToResourceUri($type, $uid) {
37        return self::RESOURCES_NS . $type . '/' . $uid;
38    }
39
40    // type functions
41
42    private static $fieldsCache = array();
43
44    private static function TYPE() {
45        return get_called_class();
46    }
47
48    private static function FIELDS() {
49        $type = static::TYPE();
50        if (!isset(self::$fieldsCache[$type])) {
51            self::$fieldsCache[$type] = static::createFIELDS();
52        }
53        return self::$fieldsCache[$type];
54    }
55
56    /** Provides the field of this object - implemented by specific type classes
57     * Retrieves an array with the specification of the fields of this object.
58     * The array contains RdfField objects. This function is called only once
59     * per type, because the result is cached.
60     */
61    protected abstract static function createFIELDS();
62
63    // instance fields
64
65    private $uid = null;
66    private $uri = null;
67    private $values = array();
68
69    public function getUri() {
70        return $this->uri;
71    }
72
73    public function getUid() {
74        return $this->uid;
75    }
76
77    public function getType() {
78        return static::TYPE();
79    }
80
81    // PHP magic functions to expose values
82
83    public function __construct($uid = null, $values = array()) {
84        if (empty($uid)) {
85            $this->uid = md5(uniqid(rand(), true));
86        } else {
87            $this->uid = $uid;
88        }
89        $this->uri = self::typeAndUidToResourceUri(static::TYPE(), $this->uid);
90        foreach ($values as $name => $value) {
91            $this->$name = $value;
92        }
93    }
94
95    private function ensure_field($name) {
96        if (!array_key_exists($name, static::FIELDS())) {
97            throw new Exception("Field " . $name . " not defined on " . self::TYPE());
98        }
99    }
100
101    private function get_field($name) {
102        self::ensure_field($name);
103        $fields = static::FIELDS();
104        return $fields[$name];
105    }
106
107    public function __isset($name) {
108        $this->ensure_field($name);
109        return isset($this->values[$name]);
110    }
111
112    public function __get($name) {
113        $field = self::get_field($name);
114        if (isset($this->values[$name])) {
115            return $this->values[$name];
116        }
117        return $field->get_null();
118    }
119
120    public function __set($name, $value) {
121        self::ensure_field($name);
122        $this->values[$name] = $value;
123    }
124
125    public function __unset($name) {
126        unset($this->values[$name]);
127    }
128
129    public function getIterator() {
130        return new ArrayIterator($this->values);
131    }
132
133    public function __toString() {
134        return self::TYPE() . '(uid=' . $this->uid . ')';
135    }
136
137    // RDF database functions
138
139    private static function get_db_filename() {
140        $fileName = 'data/' . static::TYPE() . 's/' . static::TYPE() . 's.rdf';
141        $dirName = dirname($fileName);
142        if (!is_dir($dirName)) {
143            mkdir($dirName, 0777 /* PHP's default value */, $recursive = TRUE);
144        }
145        return $fileName;
146    }
147
148    private static function get_model() {
149        $fileName = self::get_db_filename();
150        $model = ModelFactory::getDefaultModel();
151        if (file_exists($fileName)) {
152            $model->load($fileName);
153        }
154        return $model;
155    }
156
157    private static function save_model($model) {
158        $fileName = self::get_db_filename();
159        return $model->saveAs($fileName, 'rdf');
160    }
161
162    protected function evaluate() {
163        foreach (static::FIELDS() as $name => $field) {
164            if ($field->required()
165                    && (!is_set($this->values[$name])
166                    || ( $field->multiple() && empty($this->values[$name]) ) )) {
167                throw new Exception("Required field $name not found.");
168            }
169        }
170    }
171
172    public function save() {
173        $this->evaluate();
174
175        $model = self::get_model();
176
177        // create new resource and remove old saved resource
178        $resource = new Resource($this->uri);
179        $model->subtract($model->find($resource, null, null));
180
181        // set type
182        $model->add(new Statement($resource,
183                        new Resource(RDF_NAMESPACE_URI . 'type'),
184                        new Resource(self::RESOURCES_NS . static::TYPE())));
185
186        // add fields
187        foreach (static::FIELDS() as $name => $field) {
188            if (isset($this->values[$name])) {
189                $values = $this->values[$name];
190                if (!$field->multiple()) {
191                    $values = array($values);
192                }
193                foreach ($values as $value) {
194                    $type = $field->type();
195                    $type::save($model, $resource, $name, $value);
196                }
197            }
198        }
199
200        if (!self::save_model($model)) {
201            throw new Exception('Save failed: internal RDFAPI error.');
202        }
203    }
204
205    public static function get($predicates = array()) {
206        $model = self::get_model();
207
208        $query = '';
209        $query .= "PREFIX predicates: <" . self::PREDICATES_NS . ">\n";
210        $query .= "PREFIX resources: <" . self::RESOURCES_NS . ">\n";
211        $query .= "SELECT *\n";
212        $query .= "WHERE {\n";
213        $query .= "\t?object a resources:" . static::TYPE() . " .\n";
214        foreach (static::FIELDS() as $name => $field) {
215            if (!$field->multiple()) {
216                $cond = "?object predicates:" . $name . " ?" . $name;
217                if ($field->required() || array_key_exists($name, $predicates)) {
218                    $query .= "\t$cond .\n";
219                } else {
220                    $query .= "\tOPTIONAL { $cond . }\n";
221                }
222            }
223        }
224        if (!empty($predicates)) {
225            $preds = array();
226            foreach ($predicates as $name => $value) {
227                if ($name == 'uid') {
228                    if (!is_string($value)) {
229                        $value = $value->getUid();
230                    }
231                    $preds[] = 'str(?object) = "' . self::RESOURCES_NS . self::TYPE() . '/' . $value . '"';
232                } else {
233                    $type = self::get_field($name)->type();
234                    $preds[] = $type::predicate($name, $value);
235                }
236            }
237            $query .= "\tFILTER ( " . join(" && ", $preds) . " ) .\n";
238        }
239        $query .= "}\n";
240
241        $results = $model->sparqlQuery($query);
242        $objects = array();
243        if (!empty($results)) {
244            $realClass = self::TYPE();
245            foreach ($results as $result) {
246                $uri = $result['?object']->uri;
247                $values = array();
248                foreach (static::FIELDS() as $name => $field) {
249                    if (!$field->multiple()) {
250                        $queryName = "?$name";
251                        if (isset($result[$queryName]) && !empty($result[$queryName])) {
252                            $type = $field->type();
253                            $values[$name] = $type::load($result[$queryName]);
254                        }
255                    } else {
256                        $values[$name] = self::getMultiple($model, $uri, $name, $field);
257                    }
258                }
259                $info = self::resourceUriToTypeAndUid($uri);
260                $objects[] = new $realClass($info['uid'], $values);
261            }
262        }
263        return $objects;
264    }
265
266    private static function getMultiple($model, $uri, $name, $field) {
267        $query = '';
268        $query .= "PREFIX predicates: <" . self::PREDICATES_NS . ">\n";
269        $query .= "SELECT DISTINCT ?item\n";
270        $query .= "WHERE { <$uri> predicates:$name ?item . }\n";
271
272        $results = $model->sparqlQuery($query);
273        $values = array();
274        if (!empty($results)) {
275            foreach ($results as $result) {
276                $type = $field->type();
277                $values[] = $type::load($result['?item']);
278            }
279        }
280        return $values;
281    }
282
283}
284
285class RdfPointer implements RdfResource {
286
287    private $uri = null;
288    private $uid = null;
289    private $type = null;
290
291    public function __construct($uri) {
292        $this->uri = $uri;
293        $info = RdfObject::resourceUriToTypeAndUid($uri);
294        $this->uid = $info['uid'];
295        $this->type = $info['type'];
296    }
297
298    public function getUri() {
299        return $this->uri;
300    }
301
302    public function getUid() {
303        return $this->uid;
304    }
305
306    public function getType() {
307        return $this->type;
308    }
309
310    public function get() {
311        $type = $this->type;
312        return $type::get(array('uid' => $this->uid));
313    }
314
315}
316
317class RdfField {
318
319    const REQUIRED = 1;
320    const MULTIPLE = 2;
321
322    private $type;
323    private $conditions;
324
325    public function __construct($type, $conditions = array()) {
326        $this->type = $type;
327        $this->conditions = $conditions;
328    }
329
330    public function type() {
331        return $this->type;
332    }
333
334    public function required() {
335        return in_array(self::REQUIRED, $this->conditions);
336    }
337
338    public function multiple() {
339        return in_array(self::MULTIPLE, $this->conditions);
340    }
341
342}
343
344function RdfField($type, $conditions = array()) {
345    return new RdfField($type, $conditions);
346}
347
348interface RdfType {
349
350    static function save($model, $resource, $predicateUri, $value);
351
352    static function load($value);
353
354    static function predicate($name, $value);
355}
356
357class RdfString implements RdfType {
358
359    static function save($model, $resource, $name, $value) {
360        $obj = new Literal($value);
361        $model->add(new Statement($resource, new Resource(RdfObject::PREDICATES_NS . $name), $obj));
362    }
363
364    static function load($value) {
365        return $value->label;
366    }
367
368    static function predicate($name, $value) {
369        return 'str(?' . $name . ') = "' . $value . '"';
370    }
371
372}
373
374function RdfString() {
375    return new RdfString();
376}
377
378class RdfDatetime implements RdfType {
379
380    static function save($model, $resource, $name, $value) {
381        $obj = new Literal($value->getTimestamp());
382        $model->add(new Statement($resource, new Resource(RdfObject::PREDICATES_NS . $name), $obj));
383    }
384
385    static function load($value) {
386        $obj = new DateTime();
387        $obj->setTimestamp(intval($value->label));
388        return $obj;
389    }
390
391    static function predicate($name, $value) {
392        return 'str(?' . $name . ') = "' . $value . '"';
393    }
394
395}
396
397function RdfDatetime() {
398    return new RdfDatetime();
399}
400
401class RdfBoolean implements RdfType {
402
403    static function save($model, $resource, $name, $value) {
404        $model->add(new Statement($resource,
405                        new Resource(RdfObject::PREDICATES_NS . $name),
406                        new Literal((bool) $value)));
407    }
408
409    static function load($value) {
410        return (bool) $value->label;
411    }
412
413    static function predicate($name, $value) {
414        return 'str(?' . $name . ') = "' . $value . '"';
415    }
416
417}
418
419function RdfBoolean() {
420    return new RdfBoolean();
421}
422
423class RdfReference implements RdfType {
424
425    static function save($model, $resource, $name, $value) {
426        if (!is_string($value)) {
427            $value = $value->getUri();
428        }
429        $obj = new Resource($value);
430        $model->add(new Statement($resource, new Resource(RdfObject::PREDICATES_NS . $name), $obj));
431    }
432
433    static function load($value) {
434        return new RdfPointer($value->uri);
435    }
436
437    static function predicate($name, $value) {
438        return '?' . $name . ' = <' . $value . '>';
439    }
440
441}
442
443function RdfReference() {
444    return new RdfReference();
445}
446
447class Answer extends RdfObject {
448
449    protected static function createFIELDS() {
450        return array(
451            'question' => RdfField(RdfReference()),
452            'values' => RdfField(RdfString(), array(RdfField::MULTIPLE))
453        );
454    }
455
456}
457
458class AnswerSet extends RdfObject {
459
460    protected static function createFIELDS() {
461        return array(
462            'survey' => RdfField(RdfReference()),
463            'respondent' => RdfField(RdfReference()),
464            'datetime' => RdfField(RdfDatetime()),
465            'answers' => RdfField(RdfReference(), array(RdfField::MULTIPLE))
466        );
467    }
468
469}
470
471class Application extends RdfObject {
472
473    protected static function createFIELDS() {
474        return array(
475            'title' => RdfField(RdfString()),
476            'description' => RdfField(RdfString()),
477            'style' => RdfField(RdfString())
478        );
479    }
480
481}
482
483class ApplicationInstance extends RdfObject {
484
485    protected static function createFIELDS() {
486        return array(
487            'application' => RdfField(RdfReference()),
488            'starttime' => RdfField(RdfDatetime()),
489            'endtime' => RdfField(RdfDatetime()),
490            'open' => RdfField(RdfBoolean())
491        );
492    }
493
494}
495
496class Question extends RdfObject {
497
498    protected static function createFIELDS() {
499        return array(
500            'code' => RdfField(RdfString()),
501            'title' => RdfField(RdfString()),
502            'type' => RdfField(RdfString()),
503            'description' => RdfField(RdfString()),
504            'category' => RdfField(RdfString()),
505            'answers' => RdfField(RdfString(), array(RdfField::MULTIPLE))
506        );
507    }
508
509}
510
511class Session extends RdfObject {
512
513    protected static function createFIELDS() {
514        return array(
515            'title' => RdfField(RdfString()),
516            'creator' => RdfField(RdfReference()),
517            'creationdate' => RdfField(RdfDatetime()),
518            'pipeline' => RdfField(RdfReference(), array(RdfField::MULTIPLE))
519        );
520    }
521
522}
523
524class SessionInstance extends RdfObject {
525
526    protected static function createFIELDS() {
527        return array(
528            'title' => RdfField(RdfString()),
529            'location' => RdfField(RdfString()),
530            'facilitator' => RdfField(RdfReference()),
531            'starttime' => RdfField(RdfDatetime()),
532            'endtime' => RdfField(RdfDatetime()),
533            'notes' => RdfField(RdfString(), array(RdfField::MULTIPLE)),
534            'session' => RdfField(RdfReference()),
535            'surveyinstances' => RdfField(RdfReference(), array(RdfField::MULTIPLE)),
536            'applicationinstances' => RdfField(RdfReference(), array(RdfField::MULTIPLE))
537        );
538    }
539
540}
541
542class Survey extends RdfObject {
543
544    protected static function createFIELDS() {
545        return array(
546            'title' => RdfField(RdfString()),
547            'description' => RdfField(RdfString()),
548            'creator' => RdfField(RdfReference()),
549            'questions' => RdfField(RdfString(), array(RdfField::MULTIPLE))
550        );
551    }
552
553}
554
555class SurveyInstance extends RdfObject {
556
557    protected static function createFIELDS() {
558        return array(
559            'survey' => RdfField(RdfReference()),
560            'starttime' => RdfField(RdfDatetime()),
561            'endtime' => RdfField(RdfDatetime()),
562            'open' => RdfField(RdfBoolean()),
563            'presetanswers' => RdfField(RdfReference(), array(RdfField::MULTIPLE)),
564            'answersets' => RdfField(RdfReference(), array(RdfField::MULTIPLE))
565        );
566    }
567
568}
569
570class User extends RdfObject {
571
572    protected static function createFIELDS() {
573        return array(
574            'email' => RdfField(RdfString()),
575            'passwordHash' => RdfField(RdfString()),
576            'passwordSalt' => RdfField(RdfString())
577        );
578    }
579
580}
581
582?>
Note: See TracBrowser for help on using the repository browser.