DocLpc._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2006 Thomas Bopp, Thorsten Hampel, Ludger Merkens
2  *
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  *
17  * $Id: DocLpc.pike,v 1.2 2008/05/01 14:52:20 exodusd Exp $
18  */
19 inherit "/classes/Document";
20  set_content("inherit \"/classes/Script\";\n#include <macros.h>"+
21  (set_content("inherit \"/classes/Script\";\n"+
22  (set_content("inherit \"/classes/Script;\n"+
23  set_content("inherit \"/classes/Script\";\n#include <macros.h>;\n"+
24  set_content("inherit \"/classes/Script\";\n#include <macros.h>;\n"+
25 #include <classes.h>
26 #include <access.h>
27 #include <database.h>
28 #include <attributes.h>
29 #include <macros.h>
30 #include <types.h>
31 #include <classes.h>
32 #include <events.h>
33 #include <exception.h>
34 class DocLpc : public Document{
35 public:
36 
37 
38 
39 /* this object really represents a factory if executed !
40  */
41 
42 import Attributes;
43 
44 
45  mapping mRegAttributes; // registered attributes for this factory
46  array aoInstances; // Instances of this class
47  int __uploading;
48 
49 /**
50  * Initialize the document.
51  *
52  */
53 protected:
54  void
55 private:
56 init_document()
57 {
58  __uploading = 0;
59  mRegAttributes = ([ ]);
60  aoInstances = ({ });
61  add_data_storage(STORE_DOCLPC, retrieve_doclpc, restore_doclpc);
62 }
63 
64 public:
65 
66 /**
67  * Get the object class - CLASS_DOCLPC in this case.
68  *
69  */
70 int
71 get_object_class()
72 {
73  return ::get_object_class() | CLASS_DOCLPC;
74 }
75 
76 /**
77  * Destructor of this object.
78  *
79  */
80 protected:
81  void
82 delete_object()
83 {
84  aoInstances -= ({ 0 });
85 
86  foreach(aoInstances, object obj)
87  if ( objectp(obj) )
88  obj->delete(); // delete all instances
89  ::delete_object();
90 }
91 
92 public:
93 
94 /**
95  * Execute the DocLPC which functions as a factory class.
96  * The parameters must include a name 'name' and might include
97  * a 'moveto' variable to move the object.
98  *
99  * @param mapping variables - execution parameters.
100  * @return the newly created object.
101  */
102 mixed execute(mapping variables)
103 {
104  if ( objectp(_CODER) && sizeof(_CODER->get_members()) > 0 ) {
105  // check if User code is allowed, creator needs to be coder
106  // and no other user should have write access on this script
107  object creator = get_creator();
108  if ( !_CODER->is_member(creator) && !_ADMIN->is_member(creator) )
109  THROW("Unauthorized Script", E_ACCESS);
110  mapping sanc = get_sanction();
111  foreach(indices(sanc), object grp) {
112  if ( (sanc[grp] & SANCTION_WRITE ) && !_ADMIN->is_member(grp) &&
113  !_CODER->is_member(grp) && grp != _ADMIN && grp != _CODER )
114  THROW("Write access for non coder group enabled - aborting !",
115  E_ACCESS);
116  }
117  }
118 
119  try_event(EVENT_EXECUTE, CALLER, 0);
120  clean_instances();
121 
122  if ( !mappingp(variables) )
123  THROW( "No variables param to DocLpc->execute()!", E_ERROR );
124  if ( !stringp(variables->name) || variables->name == "" )
125  THROW( "No name provided to DocLpc->execute()!", E_ERROR );
126 
127  object obj;
128  master()->clear_compilation_failures();
129  object oeuid = geteuid();
130  seteuid(get_creator());
131  obj = ((program)("/DB:#"+get_object_id()+".pike"))(variables->name);
132  if ( !objectp(obj) )
133  THROW( "Failed to obtain instance for /DB:#"+get_object_id()+".pike"+
134  "with name '"+variables->name+"'!", E_ERROR );
135 
136 
137  object mv = find_object((int)variables["moveto"]);
138  if ( objectp(mv) )
139  obj->move(mv);
140 
141  if ( !stringp(variables["name"]) )
142  variables->name = "";
143  // first add to instances
144  aoInstances -= ({ 0 });
145  obj->sanction_object(query_attribute("DOC_USER_MODIFIED"), SANCTION_ALL);
146  obj->sanction_object(get_creator(), SANCTION_ALL);
147 
148  obj->set_attribute(OBJ_NAME, variables["name"]);
149  obj->set_attribute(OBJ_CREATION_TIME, time());
150  obj->set_acquire(obj->get_environment);
151  obj->set_acquire_attribute(OBJ_ICON, _Server->get_module("icons"));
152  obj->created();
153  seteuid(oeuid);
154  set_attribute(DOCLPC_INSTANCETIME, time());
155  require_save(STORE_DOCLPC);
156  run_event(EVENT_EXECUTE, CALLER, obj);
157  return obj;
158 }
159 
160 object provide_instance()
161 {
162  object o;
163  array instances = aoInstances;
164  if ( arrayp(instances) )
165  clean_instances();
166  //instances -= ({ 0 });
167  o = get_instance();
168  if ( objectp(o) )
169  return o;
170 
171  object e = master()->ErrorContainer();
172  master()->set_inhibit_compile_errors(e);
173  mixed err = catch {
174  o = execute((["name":"temp", ]));
175  };
176  master()->set_inhibit_compile_errors(0);
177  if ( err != 0 ) {
178  FATAL("While providing instance of %s\n%s, %s\n%O",
179  get_identifier(), err[0], e->get(), err[1]);
180  throw(err);
181  }
182 }
183 
184 /**
185  * Call this script - use first instance or create one if none.
186  *
187  * @param mapping vars - normal variable mapping
188  * @return execution result
189  */
190 mixed call_script(mapping vars)
191 {
192  object script = provide_instance();
193  return script->execute(vars);
194 }
195 
196 /**
197  * register all attributes for an object
198  *
199  * @param obj - the object to register attributes
200  * @see register_class_attribute
201  */
202 private:
203 private void
204 install_attributes(object obj)
205 {
206  object factory = _Server->get_factory(obj->get_object_class());
207  if ( !objectp(factory) )
208  factory = _Server->get_factory(CLASS_OBJECT);
209 
210  mapping mClassAttr = factory->get_attributes() + mRegAttributes;
211  foreach ( indices(mClassAttr), mixed key )
212  install_attribute(mClassAttr[key], obj);
213 }
214 
215 public:
216 
217 bool install_attribute(Attribute attr, object obj)
218 {
219  mixed err = catch {
220  mixed key = attr->get_key();
221  mixed def = attr->get_default_value();
222  if ( !zero_type(def) )
223  obj->set_attribute(key, def);
224  string|object acq = attr->get_acquire();
225  if ( stringp(acq) )
226  obj->set_acquire_attribute(key, obj->find_function(acq));
227  else
228  obj->set_acquire_attribute(key, acq);
229  return true;
230  };
231  FATAL("Error registering attribute: %O", err);
232 }
233 
234 
235 bool check_attribute(mixed key, mixed data)
236 {
237  Attribute a = mRegAttributes[key];
238  if ( objectp(a) )
239  return a->check_attribute(data);
240 }
241 
242 /**
243  * register attributes for the class(es) this factory creates.
244  * each newly created object will have the attributes registered here.
245  *
246  * @param Attribute attr - the new attribute to register.
247  * @param void|function conversion - conversion function for all objects
248  *
249  * @see classes/Object.set_attribute
250  * @see libraries/Attributes.pmod.Attribute
251  */
252 void
253 register_attribute(Attribute attr, void|function conversion)
254 {
255  try_event(EVENT_REGISTER_ATTRIBUTE, CALLER, attr);
256  register_class_attribute(attr, conversion);
257 
258  // register on all dependent factories too
259  array factories = values(_Server->get_classes());
260  foreach ( factories, object factory ) {
261  factory = factory->get_object();
262  if ( factory->get_object_id() == get_object_id() )
263  continue;
264  if ( search(Program.all_inherits(object_program(factory)),
265  object_program(this_object())) >= 0 )
266  factory->register_attribute(copy_value(attr), conversion);
267  }
268  run_event(EVENT_REGISTER_ATTRIBUTE, CALLER, attr);
269 }
270 
271 
272 /**
273  * Register_class_attribute is called by register_attribute,
274  * this function is local and does no security checks. All instances
275  * of this class are set to the default value and acquiring settings.
276  *
277  * @param Attribute attr - the Attribute to register for this factories class
278  * @param void|function conversion - conversion function.
279  * @see register_attribute
280  */
281 protected:
282  void register_class_attribute(Attribute attr, void|function conversion)
283 {
284  string|int key = attr->get_key();
285  MESSAGE("register_class_attribute(%O)",key);
286  Attribute pattr = mRegAttributes[key];
287  if ( pattr == attr ) {
288  MESSAGE("re-registering class attribute with same value.");
289  return;
290  }
291  foreach(aoInstances, object inst)
292  install_attribute(attr, inst);
293 
294  mRegAttributes[key] = attr;
295  require_save(STORE_DOCLPC);
296 }
297 
298 public:
299 
300 /**
301  * get the registration information for one attribute of this class
302  *
303  * @param mixed key - the attribute to describe.
304  * @return array of registered attribute data.
305  */
306 Attribute describe_attribute(mixed key)
307 {
308  return copy_value(mRegAttributes[key]);
309 }
310 
311 /**
312  * Get the source code of the doclpc, used by master().
313  *
314  * @return the content of the document.
315  */
316 string get_source_code()
317 {
318  return get_content();
319 }
320 
321 /**
322  * Get the compiled program of this objects content.
323  *
324  * @return the pike program.
325  */
326 final program get_program()
327 {
328  program p = (program)("/DB:#"+get_object_id()+".pike");
329  return p;
330 }
331 
332 /**
333  * Get an Array of Error String description.
334  *
335  * @return array list of errors from last upgrade.
336  */
337 array get_errors()
338 {
339  return master()->get_error("/DB:#"+get_object_id()+".pike") || ({ });
340 }
341 
342 /**
343  * Upgrade this script and all instances.
344  *
345  * @return -2 : no program passed, -1 : force needed, otherwise: number of
346  * dropped objects
347  */
348 int upgrade()
349 {
350  program p = get_program();
351  // MESSAGE("*** Upgrade of Program %O, %d instances\n", p, sizeof(aoInstances));
352 
353  mixed res = master()->upgrade(p);
354  if ( stringp(res) )
355  steam_error(res);
356 
357  if ( objectp(get_environment()) ) {
358  program pp;
359  catch(pp = (program)("/DB:/"+path));
360 
361  if ( programp(pp) ) {
362  mixed err = catch(master()->upgrade(pp));
363  if ( err )
364  FATAL("Error while upgrading: %O", err);
365  }
366  catch(pp = (program) ("steam:"+path));
367  if ( programp(pp) )
368  master()->upgrade(pp);
369  }
370 
371 
372  do_set_attribute(DOCLPC_INSTANCETIME, time());
373  if ( stringp(res) ) {
374  steam_error(res);
375  }
376  else {
377  foreach(aoInstances, object script) {
378  if ( !objectp(script) )
379  continue;
380  if ( functionp(script->upgrade) )
381  script->upgrade();
382  script->drop();
383  }
384  }
385 
386  return res;
387 }
388 
389 protected:
390  void content_begin()
391 {
392  __uploading = 1;
393 }
394 
395 public:
396 
397 protected:
398  void content_finished()
399 {
400  ::content_finished();
401  do_set_attribute(DOCLPC_INSTANCETIME, time());
402  mixed err = catch(upgrade());
403  __uploading = 0;
404  if ( err ) {
405  FATAL("Error when updating after content_finished(): %O\n%O",
406  err[0], err[1]);
407  }
408 }
409 
410 public:
411 
412 /**
413  * Retrieve the DocLPC data for storage in the database.
414  *
415  * @return the saved data mapping.
416  */
417 final mapping
418 private:
419 retrieve_doclpc()
420 {
421  if ( CALLER != _Database )
422  THROW("Invalid call to retrieve_data()", E_ACCESS);
423 
424  return ([
425  "RegAttributes":map(mRegAttributes, save_attribute),
426  "Instances": aoInstances,
427  ]);
428 }
429 
430 public:
431 
432 protected:
433  mapping save_attribute(Attribute attr)
434 {
435  return attr->save();
436 }
437 
438 public:
439 
440 /**
441  * Restore the data of the LPC document.
442  *
443  * @param mixed data - the saved data.
444  */
445 final void
446 private:
447 restore_doclpc(mixed data)
448 {
449  if ( CALLER != _Database )
450  THROW("Invalid call to restore_data()", E_ACCESS);
451 
452  aoInstances = data["Instances"];
453  if ( !arrayp(aoInstances) )
454  aoInstances = ({ });
455  foreach(indices(data->RegAttributes), mixed key) {
456  mixed v = data->RegAttributes[key];
457  if ( arrayp(v) ) {
458  mixed acq = v[4];
459  if ( intp(acq) && acq == 1 )
460  acq = REG_ACQ_ENVIRONMENT;
461  Attribute a = Attribute(key, v[1],v[0],v[6],acq,v[5],v[2],v[3]);
462  mRegAttributes[key] = a;
463  }
464  else {
465  Attribute a = Attribute(v->key,v->desc,v->type,v->def,v->acquire,
466  v->control, v->event_read, v->event_write);
467  mRegAttributes[key] = a;
468  }
469  }
470 }
471 
472 public:
473 
474 /**
475  * Get the existing instances of this pike program.
476  *
477  * @return array of existing objects.
478  */
479 array get_instances()
480 {
481  array instances = ({ });
482 
483  clean_instances();
484  for ( int i = 0; i < sizeof(aoInstances); i++ ) {
485  if ( objectp(aoInstances[i]) &&
486  aoInstances[i]->status() != PSTAT_FAIL_DELETED &&
487  aoInstances[i]->status() != PSTAT_FAIL_COMPILE)
488  instances += ({ aoInstances[i] });
489  }
490  return instances;
491 }
492 
493 void clean_instances()
494 {
495  aoInstances-= ({ 0 });
496 
497  foreach(aoInstances, object instance)
498  {
499  // FIXME: something manages to produce a broken object instance once in a while
500  // the broken instance appears to have a working status() but no get_class()
501  // maybe also no is_object() but that is yet untested.
502  if (!objectp(instance)
503  || instance->status() == PSTAT_FAIL_COMPILE
504  || instance->status() == PSTAT_FAIL_DELETED
505  || !instance->is_object
506  || !instance->is_object()
507  || !instance->get_object_id
508  || !instance->get_object_id())
509  {
510  aoInstances-= ({ instance });
511  instance->delete();
512  }
513  }
514 }
515 
516 object get_instance()
517 {
518  clean_instances();
519  foreach ( aoInstances, object instance )
520  {
521  // FIXME: something manages to produce a broken object instance once in a while
522  // the broken instance appears to have a working status() but no get_class()
523  // maybe also no is_object() but that is yet untested.
524  if (objectp(instance)
525  && instance->status() != PSTAT_FAIL_DELETED
526  && instance->status() != PSTAT_FAIL_COMPILE
527  && instance->is_object
528  && instance->is_object()
529  && instance->get_object_id() != 0)
530  return instance;
531  }
532  return 0;
533 }
534 
535 string describe()
536 {
537  mixed err = catch {
538  return sprintf("%s+(#%d,%s,%d,%s,%d Instances, ({ %{%O,%} }))",
539  get_identifier() || "(no identifier)",
540  get_object_id() || "(no id)",
541  master()->describe_program(object_program(this_object()))
542  || "(no program)",
543  get_object_class() || "(no class)",
544  do_query_attribute(DOC_MIME_TYPE) || "unknown",
545  sizeof(aoInstances),
546  aoInstances);
547  };
548  if ( err ) {
549  werror("%O: %O\n", err[0], err[1]);
550  throw(err);
551  }
552 }
553 
554 string get_class() { return "DocLpc"; }
555 
556 
557 {
558  // test script creation and upgrading
559  Test.test( "setting pike script content",
560  > 0 );
561 
562  object script = provide_instance();
563  if ( !Test.test( "providing script instance",
564  objectp(script) && programp(get_program()) ) )
565  return;
566 
567  if ( !Test.test( "running script",
568  return;
569 
570  script->drop();
571 
572  Test.add_test_function( test_more, 10, script, 0 );
573 }
574 
575 void test_more(object script, int test, void|int nr_tries)
576 {
577  if ( test == 2 ) {
578  if ( __uploading ) {
579  Test.add_test_function( test_more,
580  max(5,nr_tries),
581  script,
582  test,
583  nr_tries+1 );
584  return;
585  }
586  }
587  else if ( script->status() != PSTAT_DISK ) {
588  MESSAGE( "DocLpc: waiting for drop of event script (try #%d, test#%d) ... ",
589  nr_tries+1, test );
590  script->drop();
591  if ( nr_tries > 5 && script->status() == PSTAT_SAVE_PENDING )
592  MESSAGE(" DocLpc, waiting to save: Queue Size = %d",
593  _Database->get_save_size());
594  if ( nr_tries > 12 )
595  Test.failed( "additional tests", "timeout while waiting for event "
596  +"script to drop, tried %d times, status %d", nr_tries,
597  script->status());
598  else
599  Test.add_test_function( test_more,
600  max(5,nr_tries),
601  script,
602  test,
603  nr_tries+1 );
604  return;
605  }
606 
607  switch(test) {
608  case 0:
609  if ( Test.test( "testing automatic upgrading on set_content()",
610  "#include <macros.h>\n#include <database.h>\n"+
611  && (script->status() == PSTAT_DISK
612  || script->is_upgrading()),
613  "Errors: " + get_errors()*"\n"+
614  "status="+script->status() +",upgrading="+script->is_upgrading()) )
615  Test.add_test_function( test_more, 0, script, 1 );
616  break;
617  case 1:
618  // now error handling
619  if ( !Test.test( "script automatically upgraded by set_content()",
620 
621  MESSAGE("Testing Pike Script Error Handling ...");
622  Test.test( "content handling",
623  "#include <macros.h>;\n#include <database.h>;\n"+
624  Test.add_test_function( test_more, 0, script, 2);
625  break;
626  case 2:
627  if ( Test.test( "error handling",
628  (sizeof(get_errors()) > 0) ) )
629  {
630  if ( !Test.test( "script error keeps old instance",
631  return;
632  }
633  else {
634  FATAL("Failed to produce errors ?!: \n%O", get_errors());
635  FATAL("PROGAM is %O", get_program());
636 
637  }
638 
639  Test.add_test_function( test_more, 0, script, 3 );
640  break;
641  case 3:
642  if ( !Test.test( "upgrading script that had an error",
643  return;
644 
645  // test script and events - events after upgrade
646  "#include <events.h>;\n#include <database.h>;\n"+
647  get_object_id()+"), EVENT_ATTRIBUTES_CHANGE, PHASE_NOTIFY, "+
648  "attribute_callback); }\nvoid attribute_callback(object "+
649  "event) { if ( event->get_params()->data[\"__test\"] ) { "+
650  "werror(\"***** Test notify !\\n\"); set_attribute("+
651  "\"events\", do_query_attribute(\"events\")+1); } }\n");
652  //MESSAGE("Dropping event script ...");
653  Test.add_test_function( test_more, 0, script, 4 );
654  break;
655  case 4:
656  if ( !Test.test( "script upgraded for event handling",
657  objectp(res) ) )
658  return;
659 
660  //MESSAGE("Event Listener set to %O", res);
661 
662  set_attribute("__test", "ok");
663  if ( !Test.test( "simple event test",
664  script->query_attribute("events") == 1 ) )
665  return;
666 
667  //MESSAGE("Testing Event Script Events ...(%d)", test);
668  set_attribute("__test", "testing");
669  if ( !Test.test( "advanced event test",
670  script->query_attribute("events") == 2 ) )
671  return;
672 
673  //MESSAGE("--- Testing upgrade --- Events received (2)");
674  Test.test( "upgrading", upgrade() >= 0 );
675  Test.add_test_function( test_more, 0, script, 6 );
676  break;
677  default:
678  set_attribute("__test", "test");
679  if ( script->query_attribute("events") == 3 )
680  Test.succeeded( "event after upgrade", "script status: %d",
681  script->status() );
682  else
683  Test.failed( "event after upgrade",
684  "script status: %d, event result is %O",
685  script->status(), script->query_attribute("events") );
686  //MESSAGE("* DocLpc all Tests finished successfully !");
687  script->delete();
688  }
689 }
690 
691 
692 };