DocumentFactory._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: DocumentFactory.pike,v 1.2 2009/08/07 15:22:36 nicke Exp $
18  */
19 inherit "/factories/ObjectFactory";
20 #include <macros.h>
21 #include <attributes.h>
22 #include <database.h>
23 #include <events.h>
24 #include <types.h>
25 #include <classes.h>
26 #include <access.h>
27 class DocumentFactory : public ObjectFactory{
28 public:
29 
30 
31 
32 
33 
34 /**
35  * Initialization callback for the factory.
36  *
37  */
38 private:
39  void init_factory()
40 {
41  ::init_factory();
42 private:
43  init_class_attribute(DOC_LAST_MODIFIED, CMD_TYPE_TIME,
44  "last modified",
45  0, EVENT_ATTRIBUTES_CHANGE, 0,
46  CONTROL_ATTR_SERVER, 0);
47 private:
48  init_class_attribute(DOC_USER_MODIFIED, CMD_TYPE_OBJECT,
49  "last modified by user",
50  0, EVENT_ATTRIBUTES_CHANGE, 0,
51  CONTROL_ATTR_SERVER, 0);
52 private:
53  init_class_attribute(DOC_LAST_ACCESSED, CMD_TYPE_TIME,
54  "last accessed",
55  0, EVENT_ATTRIBUTES_CHANGE, 0,
56  CONTROL_ATTR_SERVER, 0);
57 private:
58  init_class_attribute(DOC_MIME_TYPE, CMD_TYPE_STRING,
59  "for example text/html",
60  0, EVENT_ATTRIBUTES_CHANGE, 0,
61  CONTROL_ATTR_SERVER, "");
62 private:
63  init_class_attribute(DOC_VERSIONS, CMD_TYPE_MAPPING,
64  "versioning of a document",
65  0, EVENT_ATTRIBUTES_CHANGE, 0,
66  CONTROL_ATTR_SERVER, ([ ]));
67 private:
68  init_class_attribute(DOC_TYPE, CMD_TYPE_STRING,
69  "the document type/extension",
70  0, EVENT_ATTRIBUTES_CHANGE, 0,
71  CONTROL_ATTR_SERVER,"");
72 private:
73  init_class_attribute(DOC_TIMES_READ, CMD_TYPE_INT, "how often read",
74  0, EVENT_ATTRIBUTES_CHANGE, 0,
75  CONTROL_ATTR_SERVER, 0);
76 private:
77  init_class_attribute(DOC_AUTHORS, CMD_TYPE_ARRAY, "Names of Authors",
78  0, EVENT_ATTRIBUTES_CHANGE, 0,
79  CONTROL_ATTR_USER, ({ }) );
80 
81 public:
82 private:
83  init_class_attribute(DOC_BIBTEX, CMD_TYPE_STRING,
84  "Bibtex entry of a publication",
85  0, EVENT_ATTRIBUTES_CHANGE, 0,
86  CONTROL_ATTR_USER, "");
87 }
88 
89 
90 /**
91  * Create a new instance of a document. The vars mapping should contain
92  * the following entries:
93  * url - the filename or
94  * name - the filename
95  * mimetype - the mime type (optional)
96  * externURL - content should be downloaded from the given URL.
97  *
98  * @param mapping vars - some variables for the document creation.
99  * @return the newly created document.
100  */
101 object execute(mapping vars)
102 {
103  string ext, doc_class, fname, mimetype;
104  array tokens;
105  object cont, obj;
106  string folder;
107 
108  string url = vars["url"];
109  if ( !stringp(url) ) {
110  url = vars["name"];
111  fname = url;
112  folder = "";
113  }
114  else {
115  fname = basename(url);
116  folder = dirname(url);
117  if ( strlen(folder) > 0 ) {
118  cont = get_module("filepath:tree")->path_to_object(folder, true);
119  if ( !objectp(cont) )
120  steam_error("The Container " + folder + " was not found!");
121  if ( !(cont->get_object_class() & CLASS_CONTAINER) )
122  steam_error("The destination path is not a container !");
123  }
124 
125  }
126 
127  try_event(EVENT_EXECUTE, CALLER, url);
128 
129  if (!mappingp(vars->attributes))
130  vars->attributes = ([ ]);
131 
132  if ( stringp(vars["mimetype"]) )
133  mimetype = vars["mimetype"];
134  else if ( stringp(vars["attributes"][DOC_MIME_TYPE]) )
135  mimetype = vars["attributes"][DOC_MIME_TYPE];
136 
137  if ( !stringp(mimetype) ||
138  mimetype == "auto-detect" ||
139  search(mimetype, "/") == -1 )
140  {
141  tokens = fname / ".";
142  if ( sizeof(tokens) >= 2 ) {
143  ext = tokens[-1]; // last token ?
144  ext = lower_case(ext);
145  }
146  else {
147  ext = "";
148  }
149  mimetype = _TYPES->query_mime_type(ext);
150  doc_class = _TYPES->query_document_class(ext);
151  }
152  else {
153  ext = "";
154  doc_class = _TYPES->query_document_class_mime(mimetype);
155  }
156  vars->attributes[DOC_MIME_TYPE] = mimetype;
157 
158  if ( vars->transient ) {
159  if ( mappingp(vars->attributes) )
160  vars->attributes[OBJ_TEMP] = 1;
161  else
162  vars->attributes = ([ OBJ_TEMP : 1 ]);
163  }
164 
165  SECURITY_LOG("creating " + doc_class);
166  if ( objectp(vars["move"]) ) {
167  obj = object_create(fname, doc_class, vars["move"],
168  vars["attributes"],
169  vars["attributesAcquired"],
170  vars["attributesLocked"],
171  vars["sanction"],
172  vars["sanctionMeta"]);
173  }
174  else if ( objectp(cont) )
175  {
176  // Object is created somewhere
177  SECURITY_LOG("Creating new object in "+ folder);
178  obj = object_create(fname, doc_class, cont,
179  vars["attributes"],
180  vars["attributesAcquired"],
181  vars["attributesLocked"],
182  vars["sanction"],
183  vars["sanctionMeta"]);
184  }
185  else {
186  SECURITY_LOG("Creating new object in void");
187  obj = object_create(
188  fname, doc_class, 0, vars["attributes"],
189  vars["attributesAcquired"], vars["attributesLocked"],
190  vars["sanction"],
191  vars["sanctionMeta"]);
192  }
193 
194  function obj_set_attribute = obj->get_function("do_set_attribute");
195 
196  if ( objectp(obj) ) {
197  obj_set_attribute(DOC_TYPE, ext);
198  obj_set_attribute(DOC_MIME_TYPE, mimetype);
199  }
200  if ( objectp(vars["acquire"]) )
201  obj->set_acquire(vars["acquire"]);
202 
203  if ( objectp(vars["content_obj"]) ) {
204  string content = vars["content_obj"]->get_content();
205  if ( stringp(content) )
206  obj->set_content(content);
207  }
208  if ( vars->content_id ) {
209  object caller = CALLER;
210  if ( !_SECURITY->valid_object(caller) )
211  steam_error("Calling object tries to change content id - not authorized!");
212  if ( caller->get_content_id() != vars->content_id )
213  steam_error("Only versioning is able to reuse content ids (caller: %O)!", caller);
214  obj->set_content_id(vars->content_id);
215  }
216 
217 
218  if ( stringp(vars["externURL"]) ) {
219  string uri, r_vars;
220  mapping va = ([ ]);
221 
222  if ( sscanf(vars->externURL, "%s?%s", uri, r_vars) == 2 ) {
223  string v, k;
224  array index = r_vars / "&";
225  if ( arrayp(index) ) {
226  foreach(index, string zuweisung) {
227  sscanf(zuweisung, "%s=%s", k, v);
228  va[k] = v;
229  }
230  }
231  else {
232  sscanf(r_vars, "%s=%s", k, v);
233  va[k] = v;
234  }
235  }
236  thread_create(download_url, obj, uri, va);
237  }
238 
239  run_event(EVENT_EXECUTE, CALLER, url);
240 }
241 
242 public:
243 
244 /**
245  * Download content from an extern URL.
246  *
247  * @param object doc - the document
248  * @param string url - the URL to download from.
249  */
250 protected:
251  void download_url(object doc, string url, void|mapping vars)
252 {
253  MESSAGE("Downloading url: %s, %O", url, vars);
254  array res = Protocols.HTTP.get_url_nice(url, vars);
255  if ( arrayp(res) ) {
256  doc->set_content(res[1]);
257  doc->set_attribute(DOC_MIME_TYPE, res[0]);
258  }
259  else
260  FATAL("An error occured while trying to download %s", url);
261 }
262 
263 public:
264 
265 int change_document_class(object doc)
266 {
267  // change class depending on mime type
268  if ( !_SECURITY->access_write(0, doc, CALLER) )
269  return 0;
270 
271  string mime = doc->query_attribute(DOC_MIME_TYPE);
272  object typesModule = get_module("types");
273  if ( !objectp(typesModule) )
274  steam_error("Unable to find required types module !");
275  string classname = typesModule->query_document_class_mime(mime);
276  if ( classname != doc->get_class() ) {
277  if ( _Persistence->change_object_class(doc, classname) ) {
278  MESSAGE("DocumentFactory: Changing class from " +
279  doc->get_class() + " to " + classname);
280  call(doc->drop, 0.0);
281  return 1;
282  }
283  FATAL("DocumentFactory: Failed to change class from "+
284  doc->get_class() + " to " + classname);
285  return 0;
286  }
287  return 0;
288 }
289 string get_identifier() { return "Document.factory"; }
290 string get_class_name() { return "Document"; }
291 int get_class_id() { return CLASS_DOCUMENT; }
292 
293 
294 {
295  object doc = execute( ([ "name" : "test it.jpg", ]) );
296  Test.test( "image mimetype in .jpg document",
297  ( search(doc->query_attribute(DOC_MIME_TYPE), "image/") == 0 ) );
298 
299  doc->delete();
300  doc = execute ( ([ "name": "test.html", "mimetype":"text/html", ]) );
301  Test.test( "text/html mimetype produces DocHTML class",
302  (doc->get_object_class() & CLASS_DOCHTML) );
303  Test.test( "got same mimetype as defined",
304  ( doc->query_attribute(DOC_MIME_TYPE) == "text/html" ) );
305  doc->set_content("test");
306 
307  Test.test("Content",
308  doc->get_content() == "test");
309 
310  object dup = doc->duplicate();
311  Test.test("Duplicated Object",
312  dup->get_object_class()==doc->get_object_class());
313  Test.test("Duplicated Name",
314  dup->get_identifier() == doc->get_identifier());
315  Test.test("Duplicated Content",
316  dup->get_content() == doc->get_content());
317 
318  doc->delete();
319  dup->delete();
320 
321  doc = execute ( ([ "name": "test.test.html", ]) );
322  Test.test( ".html ending produces DocHTML",
323  (doc->get_object_class() & CLASS_DOCHTML) );
324  Test.test( "got same mimetype as expected",
325  ( doc->query_attribute(DOC_MIME_TYPE) == "text/html" ) );
326 
327  // try to switch document type
328  doc->set_attribute(DOC_MIME_TYPE, "text/xsl");
329  // document classes are changed, but never to 'Document',
330  // because all derived classes only include some special functionality
331  string mclass = get_module("types")->query_document_class_mime("text/xsl");
332 
333  Test.test( "text/xsl produces DocXSL",
334  ( mclass == "DocXSL" ) );
335 
336  Test.test( "URL with folder submitted throws",
337  catch( execute( ([ "url": "time/money" ]) ) ) );
338 
339  object test1 = execute( ([ "name" : "test", ]) );
340  Test.test( "name test produces unknown mimetype",
341  ( test1->query_attribute(DOC_MIME_TYPE) == MIMETYPE_UNKNOWN ) );
342  // now rename
343  test1->set_attribute(OBJ_NAME, "test.xsl");
344 
345  object script = execute( ([ "name": "test.pike", ]) );
346  Test.test( ".pike provides DocLpc instance",
347  ( script->get_class() == "DocLpc" ) );
348  Test.start_test(script);
349 
350  object xsl = execute( ([ "name":"test.xsl" ]) );
351  Test.start_test(xsl);
352 
353  object cont;
354  cont = get_module("filepath:url")->path_to_object("/__test__");
355  if ( objectp(cont) )
356  cont->delete();
357 
358  cont = get_factory(CLASS_CONTAINER)->execute((["name":"test_container"]));
359  cont->set_acquire(0);
360  cont->set_attribute(OBJ_URL, "/__test__");
361  cont->move(OBJ("/"));
362 
363  Test.test("container path",
364  _FILEPATH->object_to_filename(cont) == "/test_container",
365  "Wrong path for Container: "+_FILEPATH->object_to_filename(cont));
366 
367  object file = execute( (["name":"test.txt"]) );
368  file->set_content("Hello World!");
369  file->move(cont);
370  Test.test("path",
371  file->query_attribute(OBJ_PATH)=="/test_container/test.txt",
372  "False path for test.txt in test_container: "+
373  file->query_attribute(OBJ_PATH));
374  doc->set_attribute(OBJ_NAME, "doc.xsl");
375  doc->move(cont);
376  cont->set_attribute(OBJ_NAME, "testcontainer");
377  Test.test("renamed path",
378  file->query_attribute(OBJ_PATH)=="/testcontainer/test.txt",
379  "False path for test.txt in testcontainer: " +
380  file->query_attribute(OBJ_PATH));
381 
382  if ( objectp(doc) && objectp(test1) ) {
383  // needs thread/call_out because Protocols.HTTP would block
384  Test.add_test_function( test_more, 5, doc, test1, mclass, cont, 1 );
385  }
386  else
387  Test.skipped( "additional tests", "test objects were not created" );
388 
389  return doc;
390 }
391 
392 protected:
393  void test_threaded(object cont, object doc, object test1)
394 {
395  object user = this_user() || USER("root");
396 
397  Standards.URI url = Standards.URI("https://"+_Server->query_config("web_server") + "/create.xml");
398 
399  if ( objectp(OBJ("/documents/create.xml")) ) {
400  url->user = "root";
401  url->password = user->get_ticket(time()+600);
402  Protocols.HTTP.put_url(url, CONTENTOF("/documents/create.xml"));
403  Test.test( "uploading /create.xml to "+ url->get_path_query(),
404  objectp(doc = _FILEPATH->path_to_object("/create.xml")) );
405  Test.test( "correct creator of uploaded /create.xml",
406  (objectp(doc) && doc->get_creator() == user ) );
407  Test.test( "correct mimetype of uploaded /create.xml",
408  (objectp(doc) && doc->query_attribute(DOC_MIME_TYPE) == "text/xml" ) );
409  }
410  else
411  Test.skipped( "upload", "no create.xml installed" );
412 
413  mixed query;
414  if ( stringp(_Server->query_config("web_server")) )
415  query = Protocols.HTTP.get_url("http://"+_Server->query_config("web_server") + "/__test__/");
416  if ( query ) {
417  query = (mapping)query;
418  Test.test( "fetching restricted container gives 401",
419  ( query->status == 401 ), "fetch gave %O", query->status );
420  query = (mapping)Protocols.HTTP.get_url("http://"+_Server->query_config("web_server") + "/__test__/test.txt");
421  Test.test( "fetching restricted file gives 401",
422  ( query->status == 401 ), "fetch gave %O\n data: %O",
423  query->status, query->data );
424  query = (mapping)Protocols.HTTP.get_url("http://"+_Server->query_config("web_server") + "/__test__/doc.xsl");
425  Test.test( "fetching restricted empty file gives 401",
426  ( query->status == 401 ), "fetch gave %O", query->status );
427 
428  // open it
429  url = Standards.URI("http://"+_Server->query_config("web_server") + "/__test__/test.txt");
430  url->user = "root";
431  url->password = user->get_ticket(time()+60);
432  object test = cont->get_object_byname("test.txt");
433 
434  Test.test("looking for test.txt", objectp(test));
435  if ( objectp(test) )
436  test->sanction_object(GROUP("steam"), SANCTION_READ);
437 
438  query = (mapping)Protocols.HTTP.get_url(url);
439  Test.test( "fetching published file gives 200",
440  ( query->status == 200 ), "fetch gave %O", query->status );
441  }
442  else
443  Test.skipped( "fetching restricted objects",
444  "no web_server defined" );
445 
446  if ( objectp(doc) ) doc->delete();
447  if ( objectp(test1) ) test1->delete();
448  if ( objectp(cont) ) cont->delete();
449 
450 }
451 
452 public:
453 
454 void test_more(object doc, object test1, string mclass, object cont, int nr_try )
455 {
456  werror("DocumentFactory->test_more(%O,%O,%O,%O,%O)\n",
457  doc, test1, mclass,cont,nr_try);
458  if ( doc->status() != PSTAT_DISK || test1->status() != PSTAT_DISK ) {
459  if ( nr_try > 10 ) {
460  MESSAGE("DocumentFactory: test_more() failed to drop documents !");
461  Test.skipped( "additional tests", "failed to drop documents, tried %d "
462  +"times", nr_try );
463  return;
464  }
465  MESSAGE("DocumentFactory: test_more() waiting for drop of documents !");
466  doc->drop();
467  test1->drop();
468  Test.add_test_function( test_more, 5, doc, test1, mclass, cont, nr_try+1 );
469  return;
470  }
471 
472  Test.test( "changing document class", ( doc->get_class() == mclass ),
473  "class of %O is %O, should be DocXSL",
474  doc, doc->get_class() );
475 
476  Test.add_test_function_thread( test_threaded, 0, cont, doc, test1 );
477 }
478 
479 array get_all_objects()
480 {
481  array result = ({ });
482  array classes = ({ "Document", "DocXSL", "DocLpc", "DocGraphics", "DocXML",
483  "DocHTML" });
484  foreach(classes, string clname) {
485  result += _Database->get_objects_by_class("/classes/"+clname);
486  }
487  return result;
488 }
489 
490 
491 };