PageRenderTime 76ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/perfSONAR_PS/DataModels/APIBuilder.pm

https://github.com/gitpan/perfSONAR_PS-Services-PingER
Perl | 1117 lines | 857 code | 193 blank | 67 comment | 182 complexity | 989ecefc53a97d9f3e372d961558c1ba MD5 | raw file
  1. package perfSONAR_PS::DataModels::APIBuilder;
  2. =head1 NAME
  3. perfSONAR_PS::DataModels::APIBuilder - builder utils to build binding perl objects collection
  4. =head1 DESCRIPTION
  5. single call is here with several private ones
  6. the public call is:
  7. buildAPI(<top object name>, <top object>, <path>,<API root name>);
  8. =head1 SYNOPSIS
  9. ###
  10. use perfSONAR_PS::DataModels::DataModel qw($message);
  11. use perfSONAR_PS::DataModels::APIBuilderqw(&buildAPI $API_ROOT $TOP_DIR $DATATYPES_ROOT) ;
  12. $API_ROOT = 'perfSONAR_PS';
  13. $TOP_DIR = "/tmp/API/" .$API_ROOT;
  14. $DATATYPES_ROOT = 'Datatypes';
  15. buildAPI('message', $message, '','' );
  16. ####
  17. =cut
  18. =head1 API
  19. =head2 Exported variables
  20. $API_ROOT - name of the API ( empty string by default)
  21. $TOP_DIR - top dirname of the API location( /tmp/API by default)
  22. $DATATYPES_ROOT - dirname for schema datamodel files
  23. =cut
  24. use strict;
  25. use warnings;
  26. use IO::File;
  27. use File::Path;
  28. use Data::Dumper;
  29. use Log::Log4perl qw(get_logger);
  30. BEGIN {
  31. use Exporter ();
  32. our (@EXPORT, @EXPORT_OK, %EXPORT_TAGS);
  33. use version; our $VERSION = qv('2.0');
  34. %EXPORT_TAGS = ();
  35. use base qw(Exporter);
  36. @EXPORT_OK = qw( );
  37. @EXPORT_OK =qw( &buildAPI &buildClass $DATATYPES_ROOT $API_ROOT $TOP_DIR $SCHEMA_VERSION $TEST_DIR);
  38. }
  39. our @EXPORT_OK;
  40. our ( $API_ROOT, $TOP_DIR, $DATATYPES_ROOT, $SCHEMA_VERSION, $TEST_DIR) = ('', '/tmp/API', 'Datatypes', APIBuilder->VERSION, "$TOP_DIR/../");
  41. my %known_classes = ();
  42. my %existed = ();
  43. my $logger = get_logger( "APIBuilder" );
  44. #
  45. # prints second parameter ( string) to multiple filehandles passed as arrayref
  46. #
  47. sub printMulti {
  48. my ($fharr, $msg) = @_;
  49. foreach my $fh (@{$fharr}) {
  50. print $fh $msg;
  51. }
  52. }
  53. #
  54. # for new classname, path, root and ns will check if this package already exists and
  55. # then update path and root with appended classname and return root and path
  56. # where root is the API modules tree path and path is the directory pathname
  57. # without top dir name
  58. #
  59. sub _makeAPIPath {
  60. my ($classname, $path, $root, $ns) = @_;
  61. my $classnameUP = ucfirst($classname);
  62. print "ROOT= $API_ROOT\:\:$DATATYPES_ROOT\:\:$SCHEMA_VERSION\:\:$ns$root\:\:$classnameUP\n";
  63. unless ( $existed{"$API_ROOT\:\:$DATATYPES_ROOT\:\:$SCHEMA_VERSION\:\:$ns$root\:\:$classnameUP"} ) {
  64. $path .= "/$classnameUP";
  65. $root .= "\:\:$classnameUP";
  66. $existed{ "$API_ROOT\:\:$DATATYPES_ROOT\:\:$SCHEMA_VERSION\:\:$ns$root" } = $classname;
  67. $known_classes{$classname}{$ns} = "$API_ROOT\:\:$DATATYPES_ROOT\:\:$SCHEMA_VERSION\:\:$ns$root" ;
  68. }
  69. return ($root, $path);
  70. }
  71. =head2 buildAPI
  72. builds the whole API recursively
  73. accepts four parameters
  74. - name of the root element - message by default
  75. - top hashref ( object to be built)
  76. - path ( empty by default )
  77. - root API name ( empty by default )
  78. =cut
  79. sub buildAPI {
  80. my ($name, $element, $path, $root, $parent ) = @_;
  81. my $ns = $element->{attrs}->{xmlns};
  82. ($root, $path) = _makeAPIPath($name, $path, $root, $ns );
  83. if( $element && ref($element) eq 'HASH' && $element->{attrs} ) {
  84. if (ref($element->{elements}) eq 'ARRAY') {
  85. mkpath ([ "$TOP_DIR/$DATATYPES_ROOT/$SCHEMA_VERSION/$ns/$path" ], 1, 0755) ;
  86. }
  87. foreach my $el (@{$element->{elements}}) {
  88. if(ref($el) eq 'ARRAY') {
  89. if(ref($el->[1]) eq 'HASH' && $el->[1]->{attrs}) {
  90. buildAPI($el->[0], $el->[1], $path, $root, $element );
  91. } elsif(ref($el->[1]) eq 'ARRAY') {
  92. foreach my $sub_el (@{$el->[1]}) {
  93. if(ref($sub_el) eq 'HASH' && $sub_el->{attrs}) {
  94. buildAPI($el->[0], $sub_el, $path, $root, $element );
  95. } elsif(ref($sub_el) eq 'ARRAY' && scalar @{$sub_el} == 1) {
  96. buildAPI($el->[0], $sub_el->[0], $path, $root, $element );
  97. } else {
  98. $logger->error(" Malformed definition: name=" . $el->[0] . " Dump=" . Dumper $sub_el);
  99. }
  100. }
  101. }
  102. }
  103. }
  104. buildClass( "$TOP_DIR/$DATATYPES_ROOT/$SCHEMA_VERSION/$ns/$path", "$API_ROOT\:\:$DATATYPES_ROOT\:\:$SCHEMA_VERSION\:\:$ns$root" , $name, $element, $parent);
  105. }
  106. return;
  107. }
  108. =head2 buildClass
  109. builds single class on the filesystem and corresponded test file
  110. accepts four parameters
  111. - full path to the class ( except for .pm extension)
  112. - full package name
  113. - name of the element
  114. - hashref with the element definition
  115. - hashref with parent definition if its not the root element
  116. =cut
  117. sub buildClass {
  118. my ($path, $root, $name, $element, $parent ) = @_;
  119. my $className = $root;
  120. my $fh = IO::File->new( $path . ".pm","w+");
  121. $logger->error(" Failed to open file :" . $path . ".pm") unless $fh;
  122. #------------------------------------------------------------------------------
  123. my @elements = grep(ref($_) eq 'ARRAY' && $_->[0] && $_->[1], @{$element->{elements}});
  124. my @elementnodes = grep(ref($_->[1]), @elements);
  125. my @textnodes = grep($_->[1] eq 'text' && !ref($_->[1]), @elements);
  126. my $elements_names = @elementnodes?join (" " , map { $_->[0] } @elementnodes):'';
  127. my $texts_names = @textnodes?join (" " , map { $_->[0] } @textnodes):'';
  128. my @attributes = grep(!/xmlns/, keys %{$element->{attrs}});
  129. my $attributes_names = @attributes?join " " , @attributes:'';
  130. #--------------------------------------------------------------------------------
  131. my %parent_sql = ();
  132. if($parent && ref($parent) eq 'HASH' && $parent->{sql}) {
  133. foreach my $table (keys %{$parent->{sql}}) {
  134. foreach my $field (keys %{$parent->{sql}->{$table}}) {
  135. my $value = $parent->{sql}->{$table}->{$field}->{value};
  136. $value = [$value] if ref($value) ne 'ARRAY';
  137. foreach my $possible (@{$value}) {
  138. $parent_sql{$table}{$field}{$possible}++;
  139. }
  140. }
  141. }
  142. }
  143. my %sql_pass =(); ### hash with pass through
  144. my %sql_here =(); ### hash with sql to get here
  145. # preprocessing sql config
  146. if($element->{sql}) {
  147. foreach my $table (keys %{$element->{sql}}) {
  148. foreach my $field (keys %{$element->{sql}->{$table}}) {
  149. my $value = $element->{sql}->{$table}->{$field}->{value};
  150. unless($value) {
  151. $logger->error(" SQL config malformed for element=$name table=$table field=$field, but value is missied");
  152. return;
  153. }
  154. my $condition = $element->{sql}->{$table}->{$field}->{if};
  155. my ($attr_name, $set) = $condition?$condition =~ m/^(\w+):?(\w+)?$/:('','');
  156. my $cond_string = $condition && $set?" (\$self->$attr_name eq '$set') ":$condition?" (\$self->$attr_name)":'';
  157. $value = [$value] if ref($value) ne 'ARRAY';
  158. foreach my $possible (@{$value}) {
  159. next if %parent_sql && $parent_sql{$table}{$field} && !$parent_sql{$table}{$field}{$name};
  160. if($elements_names =~ /\b$possible\b/) { #### if name of the possible element is among the members of this object the pass it there
  161. $sql_pass{$possible}{$table}{$field} = $cond_string;
  162. } else { ###### otherwise set it with some value ( text or attribute )
  163. $sql_here{$possible}{$table}{$field} = $cond_string;
  164. }
  165. }
  166. }
  167. }
  168. }
  169. #-------------------------------------------- build tests
  170. buildTest(\@elementnodes, \@attributes, $className, $name, $element);
  171. $logger->debug("\n...... List of Attributes:$attributes_names \n Texts: $texts_names \n Elements: $elements_names\n");
  172. #----------------------------------------------
  173. ( my $version = $SCHEMA_VERSION ) =~ tr/_/./;
  174. #--------------------------------------------
  175. print $fh <<EOA;
  176. package $className;
  177. use strict;
  178. use warnings;
  179. use English qw( -no_match_vars);
  180. use version; our \$VERSION = qv('$version');
  181. =head1 NAME
  182. $className - A base class, implements '$name' element from the perfSONAR_PS RelaxNG schema
  183. =head1 DESCRIPTION
  184. Object representation of the $name element.
  185. Object fields are:
  186. EOA
  187. #------------------------------------------------------------------------------
  188. map { print $fh " Scalar: $_, \n" } @attributes ;
  189. map { print $fh " Object reference: " . $_->[0] . " => type " . ref($_->[1]) . ",\n" } @elements ;
  190. print $fh <<EOB;
  191. The constructor accepts only single parameter, it could be a hashref to parameters hash or DOM with '$name' element
  192. =head1 SYNOPSIS
  193. use $className;
  194. my \$el = $className->new(\$DOM_Obj);
  195. =head1 METHODS
  196. =cut
  197. use XML::LibXML;
  198. use Scalar::Util qw(blessed);
  199. use Log::Log4perl qw(get_logger);
  200. use perfSONAR_PS::Datatypes::Element qw(getElement);
  201. use perfSONAR_PS::Datatypes::Namespace;
  202. use perfSONAR_PS::Datatypes::NSMap;
  203. use Readonly;
  204. EOB
  205. foreach my $el (@elementnodes) {
  206. foreach my $ns (keys %{$known_classes{$el->[0]}}) {
  207. print $fh "use " . $known_classes{$el->[0]}{$ns} . ";\n" if $known_classes{$el->[0]}{$ns};
  208. }
  209. }
  210. print $fh <<EOC;
  211. use Class::Accessor::Fast;
  212. use Class::Fields;
  213. use base qw(Class::Accessor::Fast Class::Fields);
  214. EOC
  215. print $fh "use fields qw(nsmap idmap refidmap $attributes_names $elements_names $texts_names ";
  216. print $fh " text " if $element->{text};
  217. print $fh ");\n";
  218. print $fh <<EOD;
  219. $className->mk_accessors($className->show_fields('Public'));
  220. =head2 new( )
  221. creates object, accepts DOM with element tree or hashref to the list of
  222. keyd parameters
  223. EOD
  224. map { print $fh " $_ => undef, \n" } @attributes ;
  225. map { print $fh " " . $_->[0] . " => " . ref($_->[1]) . ",\n" } @elementnodes;
  226. print $fh "text => 'text'\n" if $element->{text};
  227. print $fh <<EOF;
  228. =cut
  229. Readonly::Scalar our \$COLUMN_SEPARATOR => ':';
  230. Readonly::Scalar our \$CLASSPATH => '$className';
  231. Readonly::Scalar our \$LOCALNAME => '$name';
  232. sub new {
  233. my \$that = shift;
  234. my \$param = shift;
  235. my \$logger = get_logger( \$CLASSPATH );
  236. my \$class = ref(\$that) || \$that;
  237. my \$self = fields::new(\$class );
  238. \$self->nsmap(perfSONAR_PS::Datatypes::NSMap->new());
  239. EOF
  240. print $fh " \$self->nsmap->mapname( \$LOCALNAME, '" . $element->{attrs}->{xmlns} . "');\n";
  241. print $fh <<EOG;
  242. if(\$param) {
  243. if(blessed \$param && \$param->can('getName') && (\$param->getName =~ m/\$LOCALNAME\$/xm) ) {
  244. return \$self->fromDOM(\$param);
  245. } elsif(ref(\$param) ne 'HASH') {
  246. \$logger->error("ONLY hash ref accepted as param " . \$param );
  247. return;
  248. }
  249. if(\$param->{xml}) {
  250. my \$parser = XML::LibXML->new();
  251. my \$dom;
  252. eval {
  253. my \$doc = \$parser->parse_string( \$param->{xml});
  254. \$dom = \$doc->getDocumentElement;
  255. };
  256. if(\$EVAL_ERROR) {
  257. \$logger->error(" Failed to parse XML :" . \$param->{xml} . " \\n ERROR: \\n" . \$EVAL_ERROR);
  258. return;
  259. }
  260. return \$self->fromDOM( \$dom );
  261. }
  262. \$logger->debug("Parsing parameters: " . (join " : ", keys \%{\$param}));
  263. no strict 'refs';
  264. foreach my \$param_key (keys \%{\$param}) {
  265. \$self->\$param_key( \$param->{\$param_key} ) if \$self->can(\$param_key);
  266. }
  267. use strict;
  268. \$logger->debug("Done ");
  269. }
  270. return \$self;
  271. }
  272. sub DESTROY {
  273. my \$self = shift;
  274. \$self->SUPER::DESTROY if \$self->can("SUPER::DESTROY");
  275. return;
  276. }
  277. =head2 getDOM (\$)
  278. accept parent DOM
  279. return $name object DOM, generated from object contents
  280. =cut
  281. sub getDOM {
  282. my \$self = shift;
  283. my \$parent = shift;
  284. my \$logger = get_logger( \$CLASSPATH );
  285. my \$$name = getElement({name => \$LOCALNAME, parent => \$parent , ns => [\$self->nsmap->mapname( \$LOCALNAME )],
  286. attributes => [
  287. EOG
  288. #-------------------------------
  289. foreach my $attr (@attributes) {
  290. $logger->debug("_printConditional:: $attr = " . $element->{attrs}->{$attr});
  291. print $fh _printConditional( $attr, $element->{attrs}->{$attr}, 'get');
  292. }
  293. print $fh " ],\n"; # end for attributes
  294. print $fh _printConditional( 'text', $element->{text} , 'get') if ($element->{text} );
  295. print $fh " }); \n";
  296. ### deal with subelements
  297. ###
  298. ### each subel defined as [ name => obj ] or [name => [obj]] or [name => [obj1,obj2]] or [name => [[obj1],[obj2]]]
  299. ###
  300. ### just object arrayref of objects choice between two obj chiice between two obj arrayref
  301. ###
  302. foreach my $els (@elementnodes) {
  303. $logger->fatal(" What the heck: name=$name els=$els ") unless ref($els) eq 'ARRAY';
  304. my $condition = conditionParser($els->[2]);
  305. my $subname = $els->[0];
  306. $condition->{logic} .= " && " if $condition->{logic};
  307. if(ref($els->[1]) eq 'ARRAY') {
  308. if(scalar @{$els->[1]} > 1 ) {
  309. if(ref( $els->[1]->[0]) ne 'ARRAY') {
  310. printGetDOM($fh, $subname, $name, $condition->{logic});
  311. } else {
  312. printGetArrayDom($fh, $subname, $name, $condition->{logic});
  313. }
  314. } else {
  315. printGetArrayDom($fh, $subname, $name, $condition->{logic});
  316. }
  317. } elsif(ref($els->[1]) eq 'HASH') {
  318. printGetDOM($fh, $subname, $name, $condition->{logic});
  319. }
  320. }
  321. if( $texts_names ) {
  322. print $fh " foreach my \$textnode (qw/$texts_names /) {\n";
  323. print $fh " if(\$self->{\$textnode}) { \n";
  324. print $fh " my \$domtext = getElement({name => \$textnode, parent => \$$name , ns => [\$self->nsmap->mapname(\$LOCALNAME)],\n";
  325. print $fh " text => \$self->{\$textnode},\n";
  326. print $fh " });\n";
  327. print $fh " \$domtext?\$$name->appendChild(\$domtext):\$logger->error(\"Failed to append new text element \$textnode to $name \");\n";
  328. print $fh " } \n";
  329. print $fh " } \n";
  330. }
  331. print $fh " return \$$name;\n}\n";
  332. foreach my $el (@elementnodes) {
  333. my $subname = $el->[0];
  334. if(ref($el->[1]) eq 'ARRAY') {
  335. print $fh <<EOH5;
  336. =head2 add$subname()
  337. if any of subelements can be an arrray then this method will provide
  338. facility to add another element to the array and will return ref to such array
  339. or just set the element to a new one
  340. =cut
  341. sub add\u$subname {
  342. my \$self = shift;
  343. my \$new = shift;
  344. my \$logger = get_logger( \$CLASSPATH );
  345. \$self->$subname && ref(\$self->$subname) eq 'ARRAY'?push \@{\$self->$subname}, \$new:\$self->$subname([\$new]);
  346. \$logger->debug("Added new to $subname");
  347. \$self->buildIdMap; ## rebuild index map
  348. \$self->buildRefIdMap; ## rebuild ref index map
  349. return \$self->$subname;
  350. }
  351. =head2 remove\u${subname}ById()
  352. remove specific element from the array of ${subname} elements by id ( if id is supported by this element )
  353. accepts single param - id - which is id attribute of the element
  354. if there is no array then it will return undef and warninig
  355. if it removed some id then \$id will be returned
  356. =cut
  357. sub remove\u${subname}ById {
  358. my \$self = shift;
  359. my \$id = shift;
  360. my \$logger = get_logger( \$CLASSPATH );
  361. if(ref(\$self->$subname) eq 'ARRAY' && \$self->idmap->{$subname} && exists \$self->idmap->{$subname}{\$id}) {
  362. \$self->$subname->\[\$self->idmap->{$subname}{\$id}\]->DESTROY;
  363. my \@tmp = grep { defined \$_ } \@{\$self->$subname};
  364. \$self->$subname([\@tmp]);
  365. \$self->buildRefIdMap; ## rebuild ref index map
  366. \$self->buildIdMap; ## rebuild index map
  367. return \$id;
  368. } elsif(!ref(\$self->$subname) || ref(\$self->$subname) ne 'ARRAY') {
  369. \$logger->warn("Failed to remove element because ${subname} not an array for non-existent id:\$id");
  370. } else {
  371. \$logger->warn("Failed to remove element for non-existant id:\$id");
  372. }
  373. return;
  374. }
  375. =head2 get\u${subname}ByMetadataIdRef()
  376. get specific object from the array of ${subname} elements by MetadataIdRef( if MetadataIdRef is supported by this element )
  377. accepts single param - MetadataIdRef
  378. if there is no array then it will return just an object
  379. =cut
  380. sub get\u${subname}ByMetadataIdRef {
  381. my \$self = shift;
  382. my \$id = shift;
  383. my \$logger = get_logger( \$CLASSPATH );
  384. if(ref(\$self->$subname) eq 'ARRAY' && \$self->refidmap->{$subname} && exists \$self->refidmap->{$subname}{\$id}) {
  385. my \$$subname = \$self->$subname->\[\$self->refidmap->{$subname}{\$id}\];
  386. return (\$$subname->can("metadataIdRef") && \$$subname->metadataIdRef eq \$id)?\$$subname:undef;
  387. } elsif(\$self->$subname && (!ref(\$self->$subname) ||
  388. (ref(\$self->$subname) ne 'ARRAY' &&
  389. blessed \$self->$subname && \$self->$subname->can("metadataIdRef") &&
  390. \$self->$subname->metadataIdRef eq \$id))) {
  391. return \$self->$subname;
  392. }
  393. \$logger->warn("Requested element for non-existent metadataIdRef:\$id");
  394. return;
  395. }
  396. =head2 get\u${subname}ById()
  397. get specific element from the array of ${subname} elements by id ( if id is supported by this element )
  398. accepts single param - id
  399. if there is no array then it will return just an object
  400. =cut
  401. sub get\u${subname}ById {
  402. my \$self = shift;
  403. my \$id = shift;
  404. my \$logger = get_logger( \$CLASSPATH );
  405. if(ref(\$self->$subname) eq 'ARRAY' && \$self->idmap->{$subname} && exists \$self->idmap->{$subname}{\$id} ) {
  406. return \$self->$subname->\[\$self->idmap->{$subname}{\$id}\];
  407. } elsif(!ref(\$self->$subname) || ref(\$self->$subname) ne 'ARRAY') {
  408. return \$self->$subname;
  409. }
  410. \$logger->warn("Requested element for non-existent id:\$id");
  411. return;
  412. }
  413. EOH5
  414. }
  415. }
  416. print $fh <<EOH56;
  417. =head2 querySQL ()
  418. depending on config it will return some hash ref to the initialized fields
  419. for example querySQL ()
  420. accepts one optional prameter - query hashref
  421. will return:
  422. { ip_name_src => 'hepnrc1.hep.net' },}
  423. =cut
  424. sub querySQL {
  425. my \$self = shift;
  426. my \$query = shift; ### undef at first and then will be hash ref
  427. my \$logger = get_logger( \$CLASSPATH );
  428. EOH56
  429. if($element->{sql}) {
  430. print $fh " my \%defined_table = (";
  431. foreach my $table (keys %{$element->{sql}}) {
  432. print $fh " '$table' => [";
  433. foreach my $field (keys %{$element->{sql}->{$table}}) {
  434. print $fh " '$field', ";
  435. }
  436. print $fh " ], ";
  437. }
  438. print $fh " );\n";
  439. }
  440. foreach my $subname (keys %sql_pass) {
  441. foreach my $table (keys %{$sql_pass{$subname}}) {
  442. foreach my $entry (keys %{$sql_pass{$subname}{$table}}) {
  443. print $fh " \$query->{$table}{$entry}= [";
  444. foreach my $nss (keys %{ $known_classes{$subname}}) {
  445. print $fh " '$known_classes{$subname}{$nss}',";
  446. }
  447. print $fh " ];\n";
  448. }
  449. }
  450. }
  451. foreach my $subname (keys %sql_here) {
  452. foreach my $table (keys %{$sql_here{$subname}}) {
  453. foreach my $entry (keys %{$sql_here{$subname}{$table}}) {
  454. print $fh " \$query->{$table}{$entry}= [ '$className' ] if!(defined \$query->{$table}{$entry}) || ref(\$query->{$table}{$entry});\n";
  455. }
  456. }
  457. }
  458. if($elements_names) {
  459. print $fh <<EOH78;
  460. foreach my \$subname (qw/$elements_names/) {
  461. if(\$self->{\$subname} && (ref(\$self->{\$subname}) eq 'ARRAY' || blessed \$self->{\$subname})) {
  462. my \@array = ref(\$self->{\$subname}) eq 'ARRAY'?\@{\$self->{\$subname}}:(\$self->{\$subname});
  463. foreach my \$el (\@array) {
  464. if(blessed \$el && \$el->can("querySQL")) {
  465. \$el->querySQL(\$query);
  466. \$logger->debug("Quering $name for subclass \$subname");
  467. } else {
  468. \$logger->error(" Failed for $name Unblessed member or querySQL is not implemented by subclass \$subname");
  469. }
  470. }
  471. }
  472. }
  473. EOH78
  474. }
  475. if(%sql_here) {
  476. print $fh " eval { \n";
  477. print $fh " foreach my \$table ( keys \%defined_table) { \n";
  478. print $fh " foreach my \$entry (\@{\$defined_table{\$table}}) { \n";
  479. print $fh " if(ref(\$query->{\$table}{\$entry}) eq 'ARRAY') {\n";
  480. print $fh " foreach my \$classes (\@{\$query->{\$table}{\$entry}}) { \n";
  481. print $fh " if(\$classes && \$classes eq '$className' ) { \n";
  482. my $if_sub_cond = ' if ';
  483. foreach my $subname (@attributes, 'text') {
  484. if($sql_here{$subname}) {
  485. print $fh getSQLSub($sql_here{$subname}, $subname, $if_sub_cond );
  486. $if_sub_cond = ' elsif ';
  487. }
  488. }
  489. print $fh " }\n";
  490. print $fh " }\n";
  491. print $fh " }\n";
  492. print $fh " }\n";
  493. print $fh " }\n";
  494. print $fh " }; \n if (\$EVAL_ERROR) { \$logger->logcroak(\" SQL query building is failed here \" . \$EVAL_ERROR)};\n";
  495. }
  496. print $fh " return \$query;\n";
  497. print $fh "}\n";
  498. print $fh <<EOHH;
  499. =head2 merge
  500. merge with another $name ( append + overwrite if exists )
  501. we can do it differently
  502. method #1:
  503. convert to dom both objects and then get resulted object from combined dom
  504. method #2 default:
  505. through the introspection of the object
  506. =cut
  507. sub merge {
  508. my \$self = shift;
  509. my \$new_${name} = shift;
  510. my \$logger = get_logger( \$CLASSPATH );
  511. unless(\$new_${name} && blessed \$new_${name} && \$new_${name}->can("getDOM")) {
  512. \$logger->error(" Please supply defined object of $name ");
  513. return;
  514. }
  515. ### for each field ( element or attribute )
  516. ### merge elements, add if its arrayref and overwrite attribtues for the same elements
  517. ### merge only if namespace is the same
  518. foreach my \$member_name (\$new_${name}->show_fields) {
  519. ### double check if objects are the same
  520. if(\$self->can(\$member_name)) {
  521. my \$current_member = \$self->{\$member_name};
  522. my \$new_member = \$new_${name}->{\$member_name};
  523. ### check if both objects are defined
  524. if(\$current_member && \$new_member) {
  525. ### if one of them array then just add another one
  526. if(blessed \$current_member && blessed \$new_member && \$current_member->can("merge")
  527. && ( \$current_member->nsmap->mapname(\$member_name)
  528. eq \$new_member->nsmap->mapname(\$member_name) ) ) {
  529. \$current_member->merge(\$new_member);
  530. \$self->{\$member_name} = \$current_member;
  531. \$logger->debug(" Merged \$member_name , got" . \$current_member->asString);
  532. ### if its array then just push
  533. } elsif(ref(\$current_member) eq 'ARRAY'){
  534. \$self->{\$member_name}=[\$current_member, \$new_member];
  535. \$logger->debug(" Pushed extra to \$member_name ");
  536. }
  537. ## thats it, dont merge if new member is just a scalar
  538. } elsif( \$new_member) {
  539. \$self->{\$member_name} = \$new_member;
  540. }
  541. } else {
  542. \$logger->error(" This field \$member_name, found in supplied $name is not supported by $name class");
  543. return;
  544. }
  545. }
  546. return \$self;
  547. }
  548. =head2 buildIdMap()
  549. if any of subelements has id then get a map of it in form of
  550. hashref to { element}{id} = index in array and store in the idmap field
  551. =cut
  552. sub buildIdMap {
  553. my \$self = shift;
  554. my \$map = ();
  555. my \$logger = get_logger( \$CLASSPATH );
  556. EOHH
  557. if( @elementnodes ) {
  558. print $fh " foreach my \$field (qw/$elements_names/) {\n";
  559. print $fh " my \@array = ref(\$self->{\$field}) eq 'ARRAY'?\@{\$self->{\$field}}:(\$self->{\$field});\n";
  560. print $fh " my \$i = 0;\n";
  561. print $fh " foreach my \$el ( \@array) {\n";
  562. print $fh " if(\$el && blessed \$el && \$el->can(\"id\") && \$el->id) { \n";
  563. print $fh " \$map->{\$field}{\$el->id} = \$i; \n";
  564. print $fh " }\n";
  565. print $fh " \$i++;\n";
  566. print $fh " }\n";
  567. print $fh " }\n";
  568. print $fh " return \$self->idmap(\$map);\n";
  569. } else {
  570. print $fh " return;\n";
  571. }
  572. print $fh "}\n";
  573. print $fh <<EOHH23;
  574. =head2 buildrefIdMap ()
  575. if any of subelements has metadataIdRef then get a map of it in form of
  576. hashref to { element}{ metadataIdRef } = index in array and store in the idmap field
  577. =cut
  578. sub buildRefIdMap {
  579. my \$self = shift;
  580. my \%map = ();
  581. my \$logger = get_logger( \$CLASSPATH );
  582. EOHH23
  583. if( @elementnodes ) {
  584. print $fh " foreach my \$field (qw/$elements_names/) {\n";
  585. print $fh " my \@array = ref(\$self->{\$field}) eq 'ARRAY'?\@{\$self->{\$field}}:(\$self->{\$field});\n";
  586. print $fh " my \$i = 0;\n";
  587. print $fh " foreach my \$el ( \@array) {\n";
  588. print $fh " if(\$el && blessed \$el && \$el->can(\"metadataIdRef\") && \$el->metadataIdRef ) { \n";
  589. print $fh " \$map{\$field}{\$el->metadataIdRef} = \$i; \n";
  590. print $fh " }\n";
  591. print $fh " \$i++;\n";
  592. print $fh " }\n";
  593. print $fh " }\n";
  594. print $fh " return \$self->refidmap(\\\%map);\n";
  595. } else {
  596. print $fh " return;\n";
  597. }
  598. print $fh "}\n";
  599. print $fh <<EOH1;
  600. =head2 asString()
  601. shortcut to get DOM and convert into the XML string
  602. returns XML string representation of the $name object
  603. =cut
  604. sub asString {
  605. my \$self = shift;
  606. my \$dom = \$self->getDOM();
  607. return \$dom->toString('1');
  608. }
  609. =head2 registerNamespaces ()
  610. will parse all subelements and register all namepspaces within the $name namespace
  611. =cut
  612. sub registerNamespaces {
  613. my \$self = shift;
  614. my \$logger = get_logger( \$CLASSPATH );
  615. my \$nsids = shift;
  616. my \$local_nss = {reverse \%{\$self->nsmap->mapname}};
  617. unless(\$nsids) {
  618. \$nsids = \$local_nss;
  619. } else {
  620. \%{\$nsids} = ( \%{\$local_nss}, \%{\$nsids});
  621. }
  622. EOH1
  623. if( @elementnodes ) {
  624. print $fh " foreach my \$field (qw/$elements_names/) {\n";
  625. print $fh " my \@array = ref(\$self->{\$field}) eq 'ARRAY'?\@{\$self->{\$field}}:(\$self->{\$field});\n";
  626. print $fh " foreach my \$el ( \@array) {\n";
  627. print $fh " if(blessed \$el && \$el->can(\"registerNamespaces\") ) { \n";
  628. print $fh " my \$fromNSmap = \$el->registerNamespaces(\$nsids); \n";
  629. print $fh " my \%ns_idmap = \%{\$fromNSmap}; \n";
  630. print $fh " foreach my \$ns ( keys \%ns_idmap) {\n";
  631. print $fh " \$nsids->{\$ns}++\n";
  632. print $fh " }\n";
  633. print $fh " }\n";
  634. print $fh " }\n";
  635. print $fh " }\n";
  636. }
  637. print $fh " return \$nsids;\n";
  638. print $fh "}\n";
  639. print $fh <<EOH2;
  640. =head2 fromDOM (\$)
  641. accepts parent XML DOM element tree as parameter
  642. returns $name object
  643. =cut
  644. sub fromDOM {
  645. my \$self = shift;
  646. my \$logger = get_logger( \$CLASSPATH );
  647. my \$dom = shift;
  648. EOH2
  649. $logger->debug(" fromDOM for: name=$name ");
  650. foreach my $attr (@attributes) {
  651. print $fh _printConditional($attr, $element->{attrs}->{$attr}, 'from');
  652. print $fh " \$logger->debug(\" Attribute $attr= \". \$self->$attr) if \$self->$attr; \n";
  653. }
  654. print $fh _printConditional('text', $element->{text}, 'from') if ($element->{text}) ;
  655. if(@elements) {
  656. print $fh " foreach my \$childnode (\$dom->childNodes) { \n";
  657. print $fh " my \$getname = \$childnode->getName;\n";
  658. print $fh " my (\$nsid, \$tagname) = split \$COLUMN_SEPARATOR, \$getname; \n";
  659. print $fh " unless(\$nsid && \$tagname) { \n";
  660. ## print $fh " \$logger->warn(\" Undefined tag=\$getname\"); \n";
  661. print $fh " next;\n";
  662. print $fh " }\n";
  663. my $conditon_head = ' if';
  664. foreach my $els (@elementnodes) {
  665. $logger->fatal(" What the heck: name=$name els=$els ") unless ref($els) eq 'ARRAY';
  666. my $subname = $els->[0];
  667. my $condition = conditionParser($els->[2]);
  668. $condition->{logic} .= " && " if $condition->{logic};
  669. if(ref($els->[1]) eq 'ARRAY') {
  670. if(scalar @{$els->[1]} > 1 ) {
  671. foreach my $choice (@{$els->[1]}) {
  672. if(ref($choice) ne 'ARRAY') {
  673. printFromDOM($fh, $subname, $choice, 'CHOICE', $conditon_head, $condition->{logic});
  674. $conditon_head = ' elsif';
  675. } elsif(scalar @{$choice} == 1 ) {
  676. printFromDOM($fh, $subname, $choice->[0], 'ARRAY', $conditon_head, $condition->{logic});
  677. $conditon_head = ' elsif';
  678. } else {
  679. $logger->logdie(" Malformed element definition: name=$name subelement=$subname ");
  680. }
  681. }
  682. } else {
  683. printFromDOM($fh, $subname, $els->[1]->[0] , 'ARRAY',$conditon_head, $condition->{logic});
  684. }
  685. } elsif (ref($els->[1]) eq 'HASH') {
  686. printFromDOM($fh, $subname,$els->[1], 'HASH',$conditon_head, $condition->{logic});
  687. }
  688. $conditon_head = ' elsif';
  689. }
  690. if( @textnodes) {
  691. print $fh "$conditon_head (\$childnode->textContent && \$self->can(\"\$tagname\")) { \n";
  692. print $fh " \$self->{\$tagname} = \$childnode->textContent; ## text node \n";
  693. print $fh " } ";
  694. }
  695. if(@elementnodes || @textnodes) {
  696. print $fh " ### \$dom->removeChild(\$childnode); ##remove processed element from the current DOM so subclass can deal with remaining elements\n";
  697. }
  698. print $fh " }\n";
  699. print $fh " \$self->buildIdMap;\n \$self->buildRefIdMap;\n \$self->registerNamespaces;\n ";
  700. }
  701. print $fh "\n return \$self;\n}\n";
  702. print $fh <<EOJ;
  703. =head1 AUTHORS
  704. Maxim Grigoriev (FNAL) 2007-2008, maxim\@fnal.gov
  705. =cut
  706. 1;
  707. EOJ
  708. close $fh;
  709. return;
  710. }
  711. # auxiliary private function
  712. # build test file for the class
  713. #
  714. sub buildTest {
  715. my ($elementnodes, $attributes, $className, $name, $element) = @_;
  716. mkpath ([ "$TEST_DIR" ], 1, 0755);
  717. my $fhtest = IO::File->new( "$TEST_DIR$className.t" ,"w+");
  718. $logger->error(" Failed to open test suite file : $TEST_DIR$className.t") unless $fhtest;
  719. print $fhtest <<EOTA;
  720. use warnings;
  721. use strict;
  722. use Test::More 'no_plan';
  723. use Data::Dumper;
  724. use FreezeThaw qw(cmpStr);
  725. use Log::Log4perl;
  726. use_ok('$className');
  727. use $className;
  728. EOTA
  729. foreach my $el (@{$elementnodes}) {
  730. foreach my $ns (keys %{$known_classes{$el->[0]}}) {
  731. print $fhtest "use " . $known_classes{$el->[0]}{$ns} . ";\n" if $known_classes{$el->[0]}{$ns};
  732. }
  733. }
  734. print $fhtest <<EOTB;
  735. Log::Log4perl->init("$TOP_DIR/logger.conf");
  736. my \$obj1 = undef;
  737. #2
  738. eval {
  739. \$obj1 = $className->new({
  740. EOTB
  741. map { print $fhtest " '$_' => 'value_$_'," } @{$attributes};
  742. print $fhtest "})\n};\n ok( \$obj1 && \!\$EVAL_ERROR , \"Create object $className...\" . \$EVAL_ERROR);\n \$EVAL_ERROR = undef; \n";
  743. print $fhtest "#3\n";
  744. print $fhtest " my \$ns = \$obj1->nsmap->mapname('$name');\n";
  745. print $fhtest " ok(\$ns eq '". $element->{attrs}->{xmlns} . "', \" mapname('$name')... \");\n";
  746. my $testn = '4';
  747. foreach my $att (@{$attributes}) {
  748. print $fhtest "#$testn\n";
  749. print $fhtest " my \$$att = \$obj1->$att;\n";
  750. print $fhtest " ok(\$$att eq 'value_$att', \" checking accessor obj1->$att ... \");\n";
  751. $testn++;
  752. }
  753. foreach my $subel (@{$elementnodes}) {
  754. my $subel1 = (ref($subel->[1]) eq 'ARRAY')?
  755. ((ref($subel->[1]->[0]) eq 'ARRAY')?$subel->[1]->[0]->[0]:$subel->[1]->[0]):
  756. ((ref($subel->[1]) eq 'HASH')?$subel->[1]:undef);
  757. next unless $subel1;
  758. print $fhtest "#$testn\n";
  759. my $subel_name = $subel->[0];
  760. print $fhtest " my \$obj_$subel_name = undef;\n";
  761. print $fhtest " eval {\n";
  762. print $fhtest " \$obj_$subel_name = " . $known_classes{$subel_name}{$subel1->{attrs}->{xmlns}} ."->new({";
  763. map { print $fhtest " '$_' => 'value$_'," if $_ ne 'xmlns' && $subel1->{attrs}->{$_}} keys %{$subel1->{attrs}};
  764. print $fhtest "});\n";
  765. (ref($subel->[1]) eq 'ARRAY' && $#{$subel->[1]} == 0)?print $fhtest " \$obj1->add\u$subel_name(\$obj_$subel_name);\n":
  766. print $fhtest " \$obj1->$subel_name(\$obj_$subel_name);\n ";
  767. print $fhtest " }; \n";
  768. print $fhtest " ok( \$obj_$subel_name && \!\$EVAL_ERROR , \"Create subelement object $subel_name and set it ...\" . \$EVAL_ERROR);\n \$EVAL_ERROR = undef; \n";
  769. $testn++;
  770. }
  771. print $fhtest "#$testn\n";
  772. print $fhtest " my \$string = undef;\n";
  773. print $fhtest " eval {\n";
  774. print $fhtest " \$string = \$obj1->asString \n";
  775. print $fhtest " };\n";
  776. print $fhtest " ok(\$string && \!\$EVAL_ERROR , \" Converting to string XML: \$string \" . \$EVAL_ERROR);\n";
  777. print $fhtest " \$EVAL_ERROR = undef;\n";
  778. $testn++;
  779. print $fhtest "#$testn\n";
  780. print $fhtest " my \$obj22 = undef; \n";
  781. print $fhtest " eval {\n";
  782. print $fhtest " \$obj22 = $className->new({xml => \$string});\n";
  783. print $fhtest " };\n";
  784. print $fhtest " ok( \$obj22 && \!\$EVAL_ERROR , \" re-create object from XML string: \". \$EVAL_ERROR);\n";
  785. print $fhtest " \$EVAL_ERROR = undef;\n";
  786. $testn++;
  787. print $fhtest "#$testn\n";
  788. print $fhtest " my \$dom1 = \$obj1->getDOM();\n";
  789. print $fhtest " my \$obj2 = undef; \n";
  790. print $fhtest " eval {\n";
  791. print $fhtest " \$obj2 = $className->new(\$dom1);\n";
  792. print $fhtest " };\n";
  793. print $fhtest " ok( \$obj2 && \!\$EVAL_ERROR , \" re-create object from DOM XML: \". \$EVAL_ERROR);\n";
  794. print $fhtest " \$EVAL_ERROR = undef;\n";
  795. close $fhtest;
  796. }
  797. #
  798. # auxiliary private function
  799. # prints part of getSQL which maps available entries on sql request hash
  800. #
  801. sub getSQLSub {
  802. my ($sql_fields, $subname, $if_cond ) = @_;
  803. my $head_string = " $if_cond(\$self->$subname && (";
  804. my $add = ' ';
  805. foreach my $table (keys %{$sql_fields}) {
  806. $head_string .= "$add( ";
  807. my @cond_string = ();
  808. foreach my $field (keys %{$sql_fields->{$table}}) {
  809. my $cond = $sql_fields->{$table}{$field};
  810. $cond .= ' && ' if $cond;
  811. push @cond_string, " ($cond\$entry eq '$field')";
  812. }
  813. $head_string .= (join " or ", @cond_string) . ")";
  814. $add = ' || ';
  815. }
  816. $head_string .= " )) {\n";
  817. $head_string .= " \$query->{\$table}{\$entry} = \$self->$subname;\n";
  818. $head_string .= " \$logger->debug(\" Got value for SQL query \$table.\$entry: \" . \$self->$subname);\n";
  819. $head_string .= " last; \n";
  820. $head_string .= " }\n";
  821. return $head_string;
  822. }
  823. #
  824. # auxiliary private function
  825. # printing fromDOM part
  826. #
  827. #
  828. sub printFromDOM {
  829. my ($fh, $subname, $el, $type, $conditon_head, $cond_string ) = @_;
  830. my $subnameUP = ucfirst($subname);
  831. $logger->debug("Building fromDOM: type=$type subname=$subname");
  832. print $fh "$conditon_head ($cond_string\$tagname eq '$subname' && \$nsid eq '". $el->{'attrs'}{'xmlns'} . "' && \$self->can(\$tagname)) { \n";
  833. print $fh " my \$element = undef;\n";
  834. print $fh " eval {\n";
  835. print $fh " \$element = " . $known_classes{$subname}{$el->{'attrs'}{'xmlns'}} . "->new(\$childnode) \n";
  836. print $fh " };\n";
  837. print $fh " if(\$EVAL_ERROR || !(\$element && blessed \$element)) {\n";
  838. print $fh " \$logger->error(\" Failed to load and add $subnameUP : \" . \$dom->toString . \" error: \" . \$EVAL_ERROR);\n";
  839. print $fh " return;\n";
  840. print $fh " }\n";
  841. print $fh (($type eq 'ARRAY')?" (\$self->$subname && ref(\$self->$subname) eq 'ARRAY')?push \@{\$self->$subname}, \$element:\$self->$subname([\$element]);":
  842. " \$self->$subname(\$element)") . "; ### add another $subname \n";
  843. print $fh " } ";
  844. }
  845. #
  846. # auxiliary private function
  847. # printing getDom part for arrayref members ( when its more then single instance of the sublelement )
  848. #
  849. sub printGetArrayDom {
  850. my ($fh, $subname, $name, $logic) = @_;
  851. print $fh " if($logic\$self->$subname && ref(\$self->$subname) eq 'ARRAY' ) {\n";
  852. print $fh " foreach my \$subel (\@{\$self->$subname}) { \n";
  853. print $fh " if(blessed \$subel && \$subel->can(\"getDOM\")) { \n";
  854. print $fh " my \$subDOM = \$subel->getDOM(\$$name);\n";
  855. print $fh " \$subDOM?\$$name->appendChild(\$subDOM):\$logger->error(\"Failed to append $subname elements with value: \" . \$subDOM->toString ); \n";
  856. print $fh " }\n";
  857. print $fh " }\n";
  858. print $fh " }\n";
  859. }
  860. #
  861. # auxiliary private function
  862. # printing getDom part for singular object members
  863. #
  864. sub printGetDOM {
  865. my ($fh, $subname, $name, $cond_string) = @_;
  866. print $fh " if($cond_string\$self->$subname && blessed \$self->$subname && \$self->$subname->can(\"getDOM\")) {\n";
  867. print $fh " my \$${subname}DOM = \$self->$subname->getDOM(\$$name);\n";
  868. print $fh " \$${subname}DOM?\$$name->appendChild(\$${subname}DOM):\$logger->error(\"Failed to append $subname with value: \" . \$${subname}DOM->toString ); \n";
  869. print $fh " }\n";
  870. }
  871. #
  872. # auxiliary private function
  873. # will parse conditional string and return regexp and logical condition
  874. # accepted parameter: $value is string to parse
  875. # will return hashref to the resulted hash with keys: {condition , logic => , regexp }
  876. #
  877. sub conditionParser {
  878. my $value = shift;
  879. my $result = { condition => '', logic => '', regexp => ''};
  880. return $result unless $value;
  881. $value =~ s/^(scalar|enum|set|if|unless|exclude)\:?//;
  882. $result->{condition} = $1;
  883. my @list = split ",", $value unless $result->{condition} eq 'scalar';
  884. if(@list) {
  885. $result->{logic} = "(\$self->" . (join " && \$self->", @list) . ")";
  886. $result->{regexp} = " =~ m/(" . (join "|", @list) . ")\$/";
  887. if($result->{condition} eq 'unless') {
  888. $result->{logic} = "!". $result->{logic};
  889. } elsif($result->{condition} eq 'exclude') {
  890. $result->{regexp} =~ s/\=\~/\!\~/;
  891. }
  892. }
  893. return $result;
  894. }
  895. #
  896. # auxiliary private function
  897. # analyze condition and return conditional string to be used in getDOM|fromDOM
  898. # accepted parameters: $key - [attribute | 'text'], $value - condition to parse, $what - ['get' | 'from']
  899. #
  900. #
  901. sub _printConditional {
  902. my ($key, $value,$what) = @_;
  903. my $string = '';
  904. my $arrayref_signleft = ($key ne 'text')?"[":'';
  905. my $arrayref_signright = ($key ne 'text')?"]":'';
  906. my $fromDomArg = ($key ne 'text')?"\$dom->getAttribute('$key')":"\$dom->textContent";
  907. my $condition = conditionParser($value);
  908. $logger->debug("$value Enum List:: " . ( join ":", map { " $_= " . $condition->{$_}} keys %{$condition})) unless $condition->{condition} eq 'scalar';
  909. if($condition->{condition} eq 'scalar') {
  910. $string = $what eq 'get'?" $arrayref_signleft'$key' => \$self->$key$arrayref_signright,\n":
  911. " \$self->$key($fromDomArg) if($fromDomArg);\n";
  912. } elsif($condition->{condition} =~ /^if|unless$/ && $condition->{logic}) {
  913. $string = $what eq 'get'?" $arrayref_signleft '$key' => (".$condition->{logic}."?\$self->$key:undef)$arrayref_signright,\n":
  914. " \$self->$key($fromDomArg) if(" . $condition->{logic}. " && $fromDomArg);\n";
  915. } elsif($condition->{condition} =~ /enum|set|exclude/ && $condition->{regexp}) {
  916. my $regexp = $what eq 'get'?"(\$self->$key " . $condition->{regexp} . ")":"($fromDomArg " . $condition->{regexp} .")";
  917. $string = $what eq 'get'?" $arrayref_signleft'$key' => ($regexp?\$self->$key:undef)$arrayref_signright,\n":
  918. " \$self->$key($fromDomArg) if($fromDomArg && $regexp);\n";
  919. } else {
  920. $logger->fatal("Malfromed , uknown condition=" . $condition->{condition} );
  921. }
  922. return $string;
  923. }
  924. 1;