/[cvs]/nfo/php/libs/org.netfrag.glib/DesignPattern/RemoteProxy.php
ViewVC logotype

Annotation of /nfo/php/libs/org.netfrag.glib/DesignPattern/RemoteProxy.php

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.18 - (hide annotations)
Thu May 13 19:17:55 2004 UTC (20 years, 3 months ago) by jonen
Branch: MAIN
Changes since 1.17: +7 -4 lines
+ bugfix: utf8 conversion was missing at some backend-calls

1 joko 1.1 <?php
2 joko 1.3 /**
3     * This file contains the DesignPattern::RemoteProxy class
4     *
5     * @author Andreas Motl <andreas.motl@ilo.de>
6     * @package org.netfrag.glib
7     * @name DesignPattern::RemoteProxy
8     *
9 joko 1.4 *
10 joko 1.9 */
11    
12    
13     /**
14 joko 1.4 * <b>Cvs-Log:</b>
15     *
16     * <pre>
17 joko 1.1 * -------------------------------------------------------------------------
18 jonen 1.18 * $Id: RemoteProxy.php,v 1.17 2003/07/14 10:05:23 jonen Exp $
19 joko 1.1 * -------------------------------------------------------------------------
20 joko 1.2 * $Log: RemoteProxy.php,v $
21 jonen 1.18 * Revision 1.17 2003/07/14 10:05:23 jonen
22     * bugfix: added *needed* function 'getAttributes'
23     *
24 jonen 1.17 * Revision 1.16 2003/07/02 13:51:38 jonen
25     * removed debug dumper
26     *
27 jonen 1.16 * Revision 1.15 2003/04/11 01:32:21 joko
28     * renamed logging function
29     *
30 joko 1.15 * Revision 1.14 2003/04/09 02:06:45 joko
31     * errormessage now shown preformatted
32     *
33 joko 1.14 * Revision 1.13 2003/04/04 17:38:03 joko
34     * modifications regarding error-/exception-handling and -tracing
35     *
36 joko 1.13 * Revision 1.12 2003/03/29 08:01:21 joko
37     * modified ErrorBoxing
38     *
39 joko 1.12 * Revision 1.11 2003/03/28 06:44:51 joko
40     * VERBOSE mode
41     *
42 joko 1.11 * Revision 1.10 2003/03/28 03:05:54 joko
43     * more fancy debugging-output
44     *
45 joko 1.10 * Revision 1.9 2003/03/10 23:05:25 joko
46     * + fixed metadata for phpDocumentor
47     *
48 joko 1.9 * Revision 1.8 2003/03/10 22:31:56 joko
49     * + fixed metadata for phpDocumentor
50     *
51 joko 1.8 * Revision 1.7 2003/03/09 15:51:44 joko
52     * + additional metadata for Autodia
53     *
54 joko 1.7 * Revision 1.6 2003/03/05 17:28:43 joko
55     * updated docu (phpDocumentor testing....)
56     *
57 joko 1.6 * Revision 1.5 2003/03/05 17:02:22 joko
58     * updated docu (phpDocumentor testing....)
59     *
60 joko 1.5 * Revision 1.4 2003/03/05 16:32:19 joko
61     * updated docu (phpDocumentor testing....)
62     *
63 joko 1.4 * Revision 1.3 2003/03/05 16:10:17 joko
64     * updated docu (phpDocumentor testing....)
65     *
66 joko 1.3 * Revision 1.2 2003/03/05 12:14:02 joko
67     * renamed method
68     * constructor argument expansion
69     *
70 joko 1.2 * Revision 1.1 2003/03/03 22:06:46 joko
71     * refactored from Data::Driver::Proxy
72     *
73 joko 1.1 * Revision 1.3 2003/03/01 04:56:38 joko
74     * new comment style for large blocks - stolen from phpHtmlLib
75     * commit-log encapsulated into such a block
76     * introduced some documentation
77     * refactored todo (now also inside a stylish block)
78     *
79     * Revision 1.2 2003/02/13 21:48:09 joko
80     * + caching mechanisms more configurable now
81     *
82     * Revision 1.1 2003/02/09 17:23:21 joko
83     * + refactored from flib/Application/RPC/ProxyObject.php
84     *
85     * Revision 1.7 2003/02/03 03:31:38 jonen
86     * - moved '$encoder->toISO()' to 'Remote.php'
87     *
88     * Revision 1.6 2002/12/22 13:26:20 jonen
89     * + added support of tangram independent id (e.g. Data::UUID) toggled by option at conrtuctor
90     *
91     * Revision 1.5 2002/12/18 22:36:49 jonen
92     * + added support to get remote objects via backend via 'guid'
93     * + renamed '_loadBackend' to '_loadRemote'
94     * + constructor now accepts additional options:
95     * + remote: try to get object via '_loadRemote' ...
96     * + oid: use the given identifier as an 'oid' when doing '_loadRemote'
97     * + guid: use the given identifier as a 'guid' when doing '_loadRemote'
98     *
99     * Revision 1.4 2002/12/12 02:46:31 joko
100     * + state (session) gets flushed when data was successfully loaded from remote side
101     * + fixed _loadBackend
102     * + fixed flushState
103     *
104     * Revision 1.3 2002/12/06 04:12:54 joko
105     * + replaced 'xyzCache' through 'xyzProxy'
106     * + function store(...)
107     *
108     * Revision 1.2 2002/12/05 21:44:09 joko
109     * + debugging
110     *
111     * Revision 1.1 2002/12/01 17:23:58 joko
112     * + initial check-in
113     *
114     * Revision 1.11 2002/11/12 06:05:58 cvsjoko
115     * + renamed class: Text_Encode -> TextEncode
116     *
117     * Revision 1.10 2002/10/29 19:15:33 cvsjoko
118     * - moved utf8/iso-conversion to lib/utils/Text_Encode.php
119     *
120     * Revision 1.9 2002/10/26 12:32:36 cvsjoko
121     * - removed debugging via "print"
122     * + added generic logging via PEAR::Log
123     *
124     * Revision 1.8 2002/10/22 09:51:38 cvsmax
125     * + moved semi-method 'createBackend' to method $site->user->create at User.class.php
126     *
127     * Revision 1.7 2002/10/21 18:27:09 cvsjoko
128     * - manually disabled any form of caching
129     *
130     * Revision 1.6 2002/10/17 03:48:47 cvsmax
131     * + new
132     * + function _createBackend create new Backend
133     * + function _create api-create
134     *
135     * Revision 1.5 2002/10/16 03:37:54 cvsjoko
136     * + bugfix: wrong comparison in restriction to save to array
137     *
138     * Revision 1.4 2002/10/10 03:04:23 cvsjoko
139     * + no debug-output
140     *
141     * Revision 1.3 2002/10/10 03:03:27 cvsjoko
142     * + bugfix: save object to cache only if payload from backend isn't empty
143     *
144     * Revision 1.2 2002/10/10 02:40:06 cvsjoko
145     * + new level of data-caching (session and persistant)
146     * + function _loadState loads data from session
147     * + function _saveState saves data to session
148     * + function save api-save (session & backend)
149     * + function _commit saves data to backend
150     * + handy utils
151     * + function _setAttributes
152     * + function flushState
153     *
154     * Revision 1.1 2002/10/09 00:51:39 cvsjoko
155     * + new
156     * -------------------------------------------------------------------------
157 joko 1.4 * </pre>
158     *
159 joko 1.1 */
160    
161 joko 1.4
162    
163    
164     /**
165     * Load required modules:
166     *
167     */
168     loadModule('DesignPattern::Proxy');
169    
170    
171 joko 1.1 /**
172 joko 1.7 * DesignPattern::RemoteProxy -- Multiple stage data fetching and caching
173 joko 1.1 *
174     *
175 joko 1.7 * This class (DesignPattern::RemoteProxy) provides an abstract framework
176 joko 1.1 * for loading/saving arbitrary data from/to data storages interfaced
177     * by storage *proxy*-drivers.
178     * Don't mix these up with the concrete storage *handle*-drivers
179     * doing the lowlevel stuff (opening files, talking sql, etc.).
180     * These proxy-drivers are "just" wrappers around them
181     * providing a more highlevel, consistent API making
182     * it easier for Data::Driver::Proxy to do its main work:
183     *
184 joko 1.7 * quote from: http://home.earthlink.net/~huston2/dp/proxy.html
185     * "A remote proxy provides a local representative for an
186     * object that resides in a different address space. This is
187     * what the "stub" code in RPC and CORBA provides."
188     *
189     *
190 joko 1.1 * Multiple stage data fetching and caching:
191     *
192 joko 1.4 * <pre>
193     *
194 joko 1.1 * DATA, ...
195     * ... also refered to as data, should be handled as
196     * something called data.
197     *
198     * STAGES
199     * x native php4 session: serialized payload
200     * x single rdbms table: serialized payload
201     * x ... via custom database wrapper
202     * x included php-file, included php-library
203     * o object, component, package, callbacks
204     * x ... via php PEAR DB abstraction objects
205     * x remote objects (items): flat attribute hash
206     * o use Data::Object::Identity
207     * o via Data::Driver::Proxy::RPC or others
208     * o Data::Object::Encoder: encodes/decodes values
209     * o Data::Object::Interpolator: interpolates/resolves references
210     *
211     * WORK
212     * x move data transparently around between these stages
213     *
214     * TOOLS
215     *
216     * x Overview - Packages, Handlers and Stages:
217     * This is weird. ;-)
218     * - A Package can be instantiated by its namespaced classname
219     * e.g.: "mkObject('My::Package');"
220     * - A Package can be a Handler for Data::Driver::Proxy
221     * - A Handler implements exactly one Stage
222     * - Data::Driver::Proxy itself embeds these Stages:
223     * x php4-session
224     * x rdbms-table
225     * - An example Handler is Data::Driver::RPC::Remote
226     *
227     * x Handlers:
228     * x Data::Driver::RPC::Remote
229     * Interacts with a RPC::XML server (todo: talk SOAP!)
230     * using PEAR::XML::RPC.
231     * It can cache data for "disconnected mode" using the
232     * o Data::Driver::phpHtmlLib
233     * o Data::Driver::PEAR::DB
234     * x Data::Driver::PEAR::Tree (via Data::Lift)
235     *
236 joko 1.4 * </pre>
237     *
238     *
239     * An attempt to implement some software design patterns
240 joko 1.3 * --- RemoteProxyPattern
241     *
242 joko 1.7 * @link http://www.agcs.com/supportv2/techpapers/patterns/papers/tutnotes/sld017.htm
243     * @link http://home.earthlink.net/~huston2/dp/proxy.html
244     * @link http://wiki.cs.uiuc.edu/PatternStories/RemoteObject
245 joko 1.3 * @link http://c2.com/cgi-bin/wiki?ProxyPattern
246 joko 1.7 * @link http://c2.com/cgi-bin/wiki?LazyProxies
247 joko 1.3 *
248     * @author Andreas Motl <andreas.motl@ilo.de>
249     * @link http://www.netfrag.org/~joko/
250     *
251     * @copyright (c) 2003 - All Rights reserved.
252     * @license GNU LGPL (GNU Lesser General Public License)
253     * @link http://www.gnu.org/licenses/lgpl.txt
254     *
255     * @package org.netfrag.glib
256 joko 1.6 * @subpackage DesignPattern
257 joko 1.3 * @name DesignPattern::RemoteProxy
258     *
259 joko 1.4 *
260 joko 1.7 * @todo extend options to en-/disable caching via a) session and/or b) database
261     * make feature available via runtime setter-method to these options
262     * @todo PEAR::Cache for caching purposes!!!
263     * @todo refactor database access: use PEAR for this! no more 'connectdb' here!!!
264     * @todo make database connection more flexible to make possible
265     * to have different (probably named) proxy databases (besides a "main database")
266     * @todo rename this to Data::Proxy? or split into Data::Query, Data::Result and Data::Wrapper?
267     * @todo refactor this to a "RemoteObject" class!!! (inheriting from DesignPattern::RemoteObject)
268     * @todo rename this to "DesignPattern::LazyRemoteProxy"???
269 joko 1.1 *
270     */
271     class DesignPattern_RemoteProxy extends DesignPattern_Proxy {
272    
273     var $objectId;
274     // purpose ...
275     var $meta;
276     var $payload;
277     var $attributes;
278     var $backend;
279    
280     function DesignPattern_RemoteProxy($objectId = "", $options = array() ) {
281 joko 1.15 php::log(get_class($this) . "->new()", PEAR_LOG_INFO);
282 joko 1.1 global $proxy;
283    
284 joko 1.2 // 2003-03-05 - modified constructor
285     // expand objectId
286     if (is_array($objectId)) {
287     $options = $objectId[1];
288     $objectId = $objectId[0];
289     }
290    
291 joko 1.1 // trace
292     //print Dumper($objectId, $options);
293    
294     // initialization/startup
295     $this->_init_meta_options($objectId, $options);
296     $this->_init_caching();
297     $this->_init_load();
298    
299 joko 1.2 //print Dumper($this);
300    
301 joko 1.1 }
302    
303     function _init_meta_options( $objectId="", $options = array() ) {
304     $this->meta = $options;
305    
306     if ($objectId) {
307     $this->objectId = $objectId;
308    
309     // set default load mechanism
310     if ( $this->meta[remote] && ((!$this->meta[oid] && !$this->meta[guid]) || ($this->meta[oid] && $this->meta[guid])) && (!$this->meta[key]) ) {
311     $this->meta[oid] = 1;
312     }
313    
314     }
315     }
316    
317     function _init_caching() {
318    
319     if ($this->meta[cache][session]) {
320     session_register_safe("proxy");
321     }
322 joko 1.7
323     /**
324     * <!-- Autodia -->
325     * can do: (this is metadata supplied for Autodia, don't delete!)
326     * $this->backend = new DataSource_Proxy_XMLRPC()
327     *
328     */
329 joko 1.1
330     if ($this->meta[remote]) {
331     //$this->backend = mkObject('Data::Driver::RPC::Remote', $this->meta[rpcinfo]);
332 joko 1.2 $this->backend = php::mkComponent('DataSource::Proxy::XMLRPC', $this->meta[rpcinfo]);
333 joko 1.1 }
334     }
335    
336     function _init_load() {
337     if ($this->objectId) {
338     $this->load();
339     //$this->_saveProxy();
340     }
341     }
342    
343    
344     function load() {
345 joko 1.15 php::log(get_class($this) . "->load()", PEAR_LOG_INFO);
346 joko 1.1 if (!$this->_loadState()) {
347     if (!$this->_loadProxy()) {
348    
349     // just load object from remote side if its flagged to be a remote one ...
350     if ($this->meta[remote]) {
351     $this->_loadRemote();
352     }
353    
354     }
355     }
356     }
357    
358     function save($data, $type) {
359     $this->_setAttributes($data);
360     $this->_saveState();
361     if ($type == 'commit') {
362     $this->_commit();
363     }
364     if ($type == 'create') {
365     $this->_create();
366     }
367     }
368    
369     function flush() {
370     $this->flushState();
371     $this->flushProxy();
372     }
373    
374     function _commit() {
375     $this->_saveBackend($this->attributes);
376     $this->flushState();
377     $this->flushProxy();
378     }
379    
380    
381     // TODO: make this work
382     /*
383     function _create() {
384     $this->_createBackend($this->attributes);
385     $this->flushState();
386     $this->flushProxy();
387     }
388     */
389    
390 joko 1.2 function getResult() {
391 joko 1.1 if (!$this->meta[decoded]) {
392     $this->_decode();
393     $this->_saveState();
394     }
395     return $this->attributes;
396     }
397    
398     function _setAttributes($data) {
399     $this->attributes = $data;
400 jonen 1.17 }
401    
402     function getAttributes() {
403     return $this->attributes;
404 joko 1.1 }
405    
406     function flushProxy() {
407     connectdb();
408     $sql = "DELETE FROM f_proxy WHERE oid='$this->objectId'";
409     send_sql($sql);
410     }
411    
412     function flushState() {
413     global $proxy;
414     unset($proxy[$this->objectId]);
415     $this->meta[decoded] = 0;
416     }
417    
418     function _loadState() {
419     global $proxy;
420    
421     // trace
422     //print Dumper($this);
423    
424     // debug
425 joko 1.15 php::log(get_class($this) . "->_loadState()");
426 joko 1.1
427     if ($this->attributes = $proxy[$this->objectId]) {
428     //print "_loadState:" . dumpVar($this->attributes);
429     $this->meta[decoded] = 1;
430     // TODO: make a parameter from this (0 deactivates session-layer)
431     return 0;
432     }
433     }
434    
435     function _saveState() {
436     global $proxy;
437 joko 1.15 php::log(get_class($this) . "->_saveState()");
438 joko 1.1 $proxy[$this->objectId] = $this->attributes;
439     //print "_saveState: " . dumpVar($this->attributes);
440     // TODO: throw exception-message back to user if operation fails
441     }
442    
443     function _loadProxy() {
444    
445     // FIXME!
446     if (!$this->meta[cache][db]) { return; }
447    
448     // trace & debug
449     //print Dumper($this);
450 joko 1.15 php::log(get_class($this) . "->_loadProxy()");
451 joko 1.1
452     connectdb();
453     $sql = "SELECT payload FROM f_proxy WHERE oid='$this->objectId'";
454     if ($res = send_sql($sql)) {
455     $row = mysql_fetch_array($res, MYSQL_ASSOC);
456     if ($row) {
457     $this->payload = $row[payload];
458     // TODO: make a parameter from this (0 deactivates mysqldb-layer)
459     return 0;
460     }
461     }
462     }
463    
464     // TODO: use PEAR here
465     function _saveProxy() {
466    
467     // FIXME!
468     if (!$this->meta[cache][db]) { return; }
469    
470 joko 1.15 php::log(get_class($this) . "->_saveProxy()");
471 joko 1.1 connectdb();
472     if ($this->payload) {
473     //$sql = "INSERT INTO f_proxy SET payload='$this->payload' WHERE oid='$this->objectId'";
474     $sql = "INSERT INTO f_proxy SET oid='$this->objectId', payload='$this->payload'";
475     if (!send_sql($sql)) {
476     $sql = "UPDATE f_proxy SET payload='$this->payload' WHERE oid='$this->objectId'";
477     send_sql($sql);
478     }
479     }
480     }
481    
482     function _loadRemote() {
483 joko 1.15 php::log(get_class($this) . "->_loadRemote()");
484 joko 1.1
485     // trace
486     //print Dumper($this->meta);
487    
488     // TODO: test backend for reachability first (eventually cache this information and "reset" it by another party)
489    
490     // 1. check backend-handle
491     if (!$this->backend) {
492 joko 1.15 php::log(get_class($this) . "->_loadRemote: no backend handle, please check argument 'rpcinfo'", PEAR_LOG_CRIT);
493 joko 1.1 return;
494     }
495    
496     // determine backend action by metadata
497     // check for guid or oid
498     if ($this->meta[guid]) {
499     if (!$this->objectId) {
500 joko 1.15 php::log(get_class($this) . "->_loadRemote: argument 'guid' requires valid objectId", PEAR_LOG_WARNING);
501 joko 1.1 return;
502     }
503     if (!$this->meta[classname]) {
504 joko 1.15 php::log(get_class($this) . "->_loadRemote: argument 'guid' requires 'classname'", PEAR_LOG_WARNING);
505 joko 1.1 return;
506     }
507 joko 1.15 php::log(get_class($this) . "->_loadRemote: getObjectByGuid", PEAR_LOG_DEBUG);
508 joko 1.1 $args = array( guid => $this->objectId, classname => $this->meta[classname] );
509 jonen 1.18 $result = $this->backend->send('getObjectByGuid', $args, array( utf8 => 1) );
510 joko 1.1
511     } elseif ($this->meta[oid]) {
512     if (!$this->objectId) {
513 joko 1.15 php::log(get_class($this) . "->_loadRemote: argument 'oid' requires valid objectId", PEAR_LOG_WARNING);
514 joko 1.1 return;
515     }
516 joko 1.15 php::log(get_class($this) . "->_loadRemote: getObject", PEAR_LOG_DEBUG);
517 jonen 1.18 $result = $this->backend->send('getObject', $this->objectId, array( utf8 => 1) );
518 joko 1.1
519     } elseif ($this->meta[key]) {
520     if (!$this->meta[command]) {
521 joko 1.15 php::log(get_class($this) . "->_loadRemote: argument 'key' requires 'command'", PEAR_LOG_WARNING);
522 joko 1.1 return;
523     }
524     /*
525     if (!$this->meta[query]) {
526 joko 1.15 php::log(get_class($this) . "->_loadRemote: argument 'key' requires 'query'", PEAR_LOG_WARNING);
527 joko 1.1 return;
528     }
529     */
530 joko 1.15 //php::log(get_class($this) . "->_loadRemote: $this->meta[command](" . join(' ', $this->meta[query]) . ")", PEAR_LOG_DEBUG);
531 joko 1.10 //print Dumper(array($this->meta[command], $this->meta[query]));
532 jonen 1.18 $result = $this->backend->send($this->meta[command], $this->meta[query], array( utf8 => 1) );
533 joko 1.1
534     }
535    
536 joko 1.10 //print "result: " . dumpVar($result) . "<br>";
537 joko 1.1
538 joko 1.13 $status = $this->backend->status();
539 joko 1.10 //print Dumper($status);
540    
541 joko 1.13 $good = is_array($result) && sizeof($result) && $status[connected];
542 joko 1.10
543 joko 1.13 if ($good) {
544 joko 1.1
545     // FIXME: this is dangerous!
546 joko 1.13 /*
547 joko 1.1 if ($_GET[debug]) {
548     print Dumper($result);
549     }
550 joko 1.13 */
551 joko 1.1
552     $this->payload = serialize($result);
553     // ----- move this to _encode some times
554    
555     $this->_saveProxy();
556     //print "oid: $this->objectId<br>";
557     $this->flushState();
558 joko 1.10
559 joko 1.1 } else {
560 joko 1.13
561     if (constants::get('APP_MODE_DEBUG')) {
562     $this->draw_error_box($status);
563 joko 1.11 } else {
564 joko 1.13 php::maintenance('rpc', array( status => $status ) );
565 joko 1.11 }
566 joko 1.10
567 joko 1.1 }
568    
569     }
570 joko 1.13
571     function draw_error_box($status) {
572     $style = html_style("text/css", '.boxlabel_yellow { color: yellow; font-weight:bold; }');
573     $statusbox = html_div();
574     $statusbox->set_style('background: red; border: 2px black groove; width:640px; padding:10px; margin:40px;');
575     $statusbox->add( html_span('boxlabel_yellow', "Method:"), get_class($this) . "->_loadRemote", html_br() );
576     $statusbox->add( html_span('boxlabel_yellow', "Connected:"), $status[connected], html_br() );
577     $statusbox->add( html_span('boxlabel_yellow', "RPCSESSID:"), $status[RPCSESSID], html_br() );
578     foreach ($status[errors] as $error) {
579 joko 1.14 $msg = html_pre($error[message]);
580     $statusbox->add( html_span('boxlabel_yellow', "Error($error[code]):"), $msg );
581 joko 1.13 }
582 joko 1.1
583 joko 1.13 $message = "Error while talking to remote side. Please check wire, socket or api.";
584 joko 1.15 php::log($message, PEAR_LOG_CRIT);
585 joko 1.13 $statusbox->add( html_span('boxlabel_yellow', "Critical:"), $message, html_br() );
586    
587     // V1
588     /*
589     if (constants::get('VERBOSE') || constants::get('ERRORS_ONLY')) {
590     print $style->render();
591     print $statusbox->render();
592     } else {
593     foreach ($status[errors] as $error) {
594     print Dumper($error);
595     }
596     }
597     */
598    
599     // V2
600     trace( container($style, $statusbox) );
601    
602     }
603    
604    
605 joko 1.1 function _saveBackend($result) {
606 joko 1.15 php::log(get_class($this) . "->_saveBackend()");
607 joko 1.1
608     //$encoder = new TextEncode($result);
609     //$encoder->toUTF8();
610    
611     // check for guid or oid
612     if($this->meta[guid]) {
613     $args = array( 'guid' => $this->objectId, 'classname' => $this->meta[classname], 'data' => $result );
614     $response = $this->backend->send('saveObjectByGuid', $args, array( utf8 => 1) );
615     }
616     if($this->meta[oid]) {
617     $response = $this->backend->send('saveObject', array('oid' => $this->objectId, 'data' => $result), array( utf8 => 1) );
618     }
619     }
620    
621     function _decode() {
622     // fill attributes-hashtable from message-hashtable
623     if (!$this->payload) { return; }
624     //if ($this->attributes = $backend->decodeData($this->payload)) {
625     if ($this->attributes = unserialize($this->payload)) {
626     $this->meta[decoded] = 1;
627     }
628     }
629    
630     function store($struct) {
631     $this->payload = serialize($struct);
632     $this->_saveProxy();
633     }
634    
635     }
636    
637 jonen 1.16 ?>

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed