package GO::Object::Generic;
## This is a generic object
use strict;
use lib '/Users/gwg/go/scratch/tools';
#use Carp;
#use Exporter;
use Data::Dumper;
use GO::TestSet qw(dfv_test);
use base 'GO::MsgLite';
use Data::FormValidator;
use Data::FormValidator::Constraints qw(:closures);
use vars qw($AUTOLOAD);
### DFV STUFF ###
# all possible DFV keys
sub __all_dfv_keys {
my $self = shift;
return (
$self->__single_param_hash_dfv_keys,
$self->__single_param_arr_dfv_keys,
$self->__multi_param_dfv_keys,
$self->__global_dfv_keys,
);
}
# global switches
sub __global_dfv_keys {
return (
'filters', # arr
'untaint_all_constraints', # bool
'missing_optional_valid', # bool
'validator_packages', # arr
'msgs', # hash
'debug', # bool
# 'required_regexp', # scalar
# 'optional_regexp', # scalar
);
}
# these apply to several params
sub __multi_param_dfv_keys {
return (
'require_some', # hash (made up key)
'dependency_groups', # hash ( ditto )
# 'defaults_regexp_map', # hash
# 'field_filter_regexp_map', # hash
# 'constraint_method_regexp_map', # hash
# 'untaint_regexp_map', # hash
);
}
# these can be specified on a per-param basis
sub __single_param_hash_dfv_keys {
return (
'dependencies', # hash
'defaults', # hash
'field_filters', # hash
'constraint_methods', # hash
);
}
sub __single_param_arr_dfv_keys {
return (
'required', # arr
'optional', # arr
'untaint_constraint_fields', # arr
);
}
sub __valid_form_data_keys {
return (
'human_name',
'list_items',
'human_name',
'subtext',
'list_values',
'list_values_human_name_h',
'allow_multiple',
'test',
'form_input_type',
# 'required',
# 'optional',
);
}
=head2 _object_data
All data pertaining to an object. Split into two sections, dfv_profile and specification
{ __DFV_PROFILE => { }, __PARAM_SPEC => [ ] }
=cut
sub _object_data {
my $self = shift;
if ($self->{__DFV_PROFILE} || $self->{__OBJECT_SPEC})
{ return {
dfv_profile => $self->{__DFV_PROFILE},
specification => $self->{__OBJECT_SPEC}
};
}
elsif ($self->{dfv_data} || $self->{form_data})
{ $self->_organize_obj_data;
return {
dfv_profile => $self->{__DFV_PROFILE},
specification => $self->{__OBJECT_SPEC}
};
}
return {};
}
=head2 _organize_obj_data
Sort out existing object data into a DFV profile and a spec
=cut
sub _organize_obj_data {
my $self = shift;
my $obj_data = $self->_object_data || return;
return {} if ! $obj_data->{dfv_data} && ! $obj_data->{form_data};
my $profile;
## process the dfv_data
if ($obj_data->{dfv_data} && %{$obj_data->{dfv_data}})
{ foreach (@{$self->__all_dfv_keys})
{ if ($obj_data->{dfv_data}{$_})
{ $profile->{$_} = $obj_data->{dfv_data}{$_};
}
}
}
my $reqd_h;
## deparse dfv require_some param
if ($profile->{require_some})
{ foreach my $r (values %{$profile->{require_some}})
{ if ( ! ref( $r ) || ref( $r ) ne 'ARRAY' )
{ if ($r->[0] =~ /^\d+$/)
{ shift @$r;
}
foreach (@$r)
{ $reqd_h->{$_} = 'some';
}
}
else
{ warn "$_ should be of type ARRAY\n";
}
}
}
foreach my $r ('required', 'optional')
{ if ($profile->{$r})
{ $reqd_h->{$_} = $r foreach @{$profile->{$r}};
}
}
my $spec;
## now let's see what form data we have
if ($obj_data->{form_data})
{ if (ref $obj_data->{form_data} ne 'ARRAY')
{ # if we have a different data struct, convert it into a list
if ($obj_data->{form_data}{order} && @{$obj_data->{form_data}{order}} && $obj_data->{form_data}{data_h} && %{$obj_data->{form_data}{data_h}})
{ foreach my $p (@{$obj_data->{form_data}{order}})
{ if ($obj_data->{form_data}{data_h}{$p})
{ $p = { %{$obj_data->{form_data}{data_h}{$p}}, id => $p };
}
else
{ $p = { id => $p };
}
}
$obj_data->{form_data} = $obj_data->{form_data}{order};
}
}
if (@{$obj_data->{form_data}})
{ foreach my $data (@{$obj_data->{form_data}})
{ warn "No ID found for spec param!\n" && next unless $_->{id};
my $p = $data->{id};
$spec->{by_param}{$p} = $data;
# copy the defaults, dependencies, field_filters and constraint_methods
# the form_data takes precedence at the moment
foreach ( $self->__single_param_hash_dfv_keys )
{ if ($data->{$_}) # && ! $profile->{$_}{$p})
{ $profile->{$_}{$p} = $data->{$_};
}
elsif ($profile->{$_}{$p}) # && ! $data->{$_})
{ $spec->{by_param}{$p}{$_} = $profile->{$_}{$p};
}
}
# copy the tests
if ($data->{test}) # && ! $profile->{constraint_methods}{$p})
{ $profile->{constraint_methods}{$p} = $data->{test};
}
# whether or not the field is optional / required / etc.
if ($reqd_h->{$p}) # specified in the DFV profile
{ # mark to show that the field is required
if ($reqd_h->{$p} eq 'required')
{ $spec->{by_param}{$p}{required} = 1;
}
}
else
{ my $opt;
foreach my $r ('required', 'optional')
{ if ($data->{$r}) # put this data in the DFV profile
{ push @{$profile->{$r}}, $p;
$opt = $r;
last;
}
}
if (!$opt && ! $reqd_h->{$p} ) # opt isn't yet specified
{ # set it to optional
push @{$profile->{optional}}, $p;
}
}
push @{$spec->{ordered}}, $spec->{by_param}{$p};
}
}
else
{ warn "Help! I can't decode this profile!";
}
}
$obj_data->{__DFV_PROFILE} = $self->{__DFV_PROFILE} = $profile;
$obj_data->{__OBJECT_SPEC} = $self->{__OBJECT_SPEC} = $spec;
}
=head2 _specification
Data structure containing the object specification
=cut
sub _specification {
my $self = shift;
# get all our object data
my $obj_data = $self->_object_data;
# see if the spec has already been generated. Return it if so.
if ($obj_data && $obj_data->{specification})
{ return $obj_data->{specification};
}
# return empty list otherwise
return [];
}
=head2 _dfv_data
Return a hash containing any extra dfv data which won't fit into the spec
sub _dfv_data {
# could be any of the fields in __global_dfv_keys or __multi_param_dfv_keys
return {};
}
=cut
=sub dfv_profile
Reads in the spec and returns the profile in a Data::FormValidator-compatible
format.
input: $self
output: a DFV format hash
=cut
sub dfv_profile {
my $self = shift;
# my $param_names = shift;
# get all our object data
my $obj_data = $self->_object_data;
return $obj_data->{dfv_profile} || {};
# # see if the DFV profile has already been generated. Return it if so.
# if ($obj_data && $obj_data->{dfv_profile})
# { return $obj_data->{dfv_profile};
# }
}
=head2 get_spec
Get the specification for the object
Additional argument gets the spec as an ordered array
input: $self, additional parameter if it should be returned ordered
output: if ordered is set, returns the spec as an arrayref
otherwise, returns spec as a hashref
=cut
sub get_spec {
my $self = shift;
my $ordered = shift;
my $spec = $self->_specification;
if ($ordered)
{ return $spec->{ordered};
}
else
{ my %object_spec_h = ($spec->{by_param});
return \%object_spec_h;
}
}
=head2 get_spec_for_param
Get the specification for a certain parameter of the object
=cut
sub get_spec_for_param {
my $self = shift;
my $param = shift || return;
my $object_spec = shift || $self->get_spec();
return $object_spec->{$param} || undef;
}
=head2 get_name_for_param
Get the proper name for a certain parameter of the object
UNUSED
sub get_name_for_param {
my $self = shift;
my $param = shift || return;
my $object_spec = shift || $self->get_spec();
return $object_spec->{$param}{name} if $object_spec->{$param}{name};
$param =~ s/_/ /g;
return ucfirst $param;
}
=cut
=head2 get_valid_params
Get the valid parameters for the object
(in the order in which they appear in the spec)
=cut
sub get_valid_params {
my $self = shift;
my $object_spec_list = $self->get_spec('ordered');
return [ map { $_->{id} } @$object_spec_list ];
}
##
sub is_valid_param_p {
my $self = shift;
my $param = shift;
my $object_spec = $self->get_spec();
return 1 if $object_spec->{$param};
return;
}
### Errors ###
=head2 create_error_message
Create an error message from a D::FV::R object
input: $self, $arg_h->{results}, # D::FV::R results
output: an error message
=cut
sub create_error_message {
my $self = shift;
my $arg_h = shift;
$self->startme();
$self->debugme("arg_h: ".Dumper($arg_h));
return if ! defined $arg_h->{results};
return join "
", @{GO::Utilities::summarize_errors($arg_h->{results})};
}
### Creating objects and adding parameters ###
=head2 new
Create a new object using the params in @_ as the values
input: object type, arg_h containing data for the object
arg_h should look this:
- data
- object_spec # optional
- dfv_profile # optional
- transform_data # optional; if present, the data should be transformed
- check_input # optional; input will be checked if present
output: self (hopefully with plenty of data!)
output: the object created
=cut
sub new {
my $proto = shift;
# create and bless the object
my $obj = &_create_and_bless($proto);
# initialise the object using the params in @_
return $obj->_initialise(@_);
}
=head2 _create_and_bless
Basic sub to create a new object in a certain class
input: object type
output: blessed object of that type
=cut
sub _create_and_bless {
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = {};
bless $self, $class;
return $self;
}
=head2 _initialise
Given an object, data and the object spec, goes through and adds data
from a hash to the object
input: self, arg_h containing
- data
- object_spec # optional
- dfv_profile # optional
- transform_data # optional; if present, the data should be transformed
- check_input # optional; input will be checked if present
- return_as = 'success_hash' # optional
output: self (hopefully with plenty of data!)
=cut
sub _initialise {
my $self = shift;
# $self->startme();
my $arg_h = shift;
my $return_success_hash;
$return_success_hash = 1 if $arg_h->{return_as} && $arg_h->{return_as} eq 'success_hash';
# get the data
# if it isn't in the preferred input format of $arg_h->{data},
# assume $arg_h contains the data
my $data_h = $arg_h->{data} || $arg_h || {};
if ($arg_h->{transform_data})
{ # we need to transform the data before doing anything else
$data_h = $self->transform_parsed_data($arg_h);
}
my $results;
# see if we are going to be checking the input or not
if ($arg_h->{check_input})
{ ## get the DFV profile
my $profile = $arg_h->{dfv_profile} || $self->dfv_profile;
## validate the input hash data
my $results = Data::FormValidator->check( $data_h, $profile );
if (!$results->success)
{ # create an error message and add it to the return_h
my $err_msg = $self->create_error_message({ input => $data_h, results => $results });
$self->printerr("new [ ". $self ." ] failed the test:\n$err_msg");
$self->debugme("broken object: ".Dumper($data_h)."\n");
if ($return_success_hash)
{ return { ERROR_LIST => [ { CLASS => 'fatal', MSG => "$self object creation failed: ".$err_msg, CALLER => (caller(0))[3], OBJECT => $data_h, RESULTS_OBJECT => $results }, ], };
}
else
{ return $self;
}
}
## put the valid results into the $arg_h->{data}
$arg_h->{data} = $results->valid;
}
my $object_spec = $arg_h->{object_spec} || $self->get_spec();
# go through the data;
# there may be some pieces of data that need to be dealt with
# in a particular way. Other than that, we can just add the params
foreach my $param (keys %$object_spec)
{ my $value = $self->create_param_data( $param, $data_h );
# if there's no data, go on to the next one
next if ! defined $value;
$self->set_param({ param => $param, value => $value, param_spec => $object_spec->{$param}, add => 1 });
}
if ($return_success_hash)
{ $results = { SUCCESS => 1, OBJECT => $self };
}
else
{ $results = $self;
}
return $results;
}
=head2 transform_parsed_data
input: $self, $arg_h containing
- data # the data hash
- object_spec # object specification (optional)
output: the data hash with any appropriate transformations done on it
This is for transforming parsed data BEFORE any error checking occurs
=cut
sub transform_parsed_data {
my $self = shift;
my $arg_h = shift;
my $data_h = $arg_h->{data};
## do any transformations here
return $data_h;
}
=head2 add_param
Add a value to a parameter
input: self, arg_h hash containing
- value # the value
- param # param to which the value should be added
- object_spec # the full object spec (optional)
- param_spec # the spec for that parameter (optional)
output: self->param or undefined if the param does not exist
=cut
sub add_param {
my $self = shift;
my $arg_h = shift;
return $self->set_param({ %$arg_h, add => 1 });
}
=head2 set_param
Set a parameter (overriding an existing parameter if appropriate)
input: self, arg_h hash containing
- value # the value
- param # param to which the value should be added
- object_spec # the full object spec (optional)
- param_spec # the spec for that parameter (optional)
- add # if present, will add the param to an existing list, rather than
# replacing the list
output: self->param or undefined if the param does not exist
=cut
sub set_param {
my $self = shift;
my $arg_h = shift;
return undef unless $arg_h->{param} && $arg_h->{value};
my $do_this = "set";
if ($arg_h->{add})
{ $do_this = "add";
}
my $method = $do_this."_".$arg_h->{param};
if (exists &$method)
{ return $self->$method($arg_h);
}
my $param_spec = $arg_h->{param_spec} ||
$arg_h->{object_spec}{$arg_h->{param}} ||
$self->get_spec_for_param($arg_h->{param});
if (!$param_spec)
{ ## no spec present... we can't add this param to the object
$self->debugme("error! Cannot $do_this param ".$arg_h->{param}." to the object.");
return undef;
}
# check the param if required
if ($arg_h->{check_param})
{ return undef unless $self->check_param($arg_h);
}
$arg_h->{value} = [ $arg_h->{value} ] if ref( $arg_h->{value} ) ne 'ARRAY';
# params that are lists
if ($param_spec->{allow_multiple} && $param_spec->{allow_multiple} == 1)
{ if ($do_this eq 'add')
{ push @{$self->{ $arg_h->{param} }}, @{$arg_h->{value}};
}
else
{ $self->{ $arg_h->{param} } = $arg_h->{value};
}
}
else
{ $self->{ $arg_h->{param} } = $arg_h->{value}[0];
}
return $self->{ $arg_h->{param} };
}
=head2 create_param_data
Sub to allow any jiggery-pokery that might need to be done to data for a param.
Occurs AFTER error checking. Do we need this?
input: self, parameter, data
output: the data for the parameter
=cut
sub create_param_data {
my $self = shift;
my ($param, $data) = @_;
# any special stuff that needs to be done, e.g. making objects, etc., goes here
return $data->{$param} || undef;
}
=head2 check_param
Check that the data for a parameter is OK
input: self, arg_h containing
- param
- value
output: 1 if the value(s) is/are valid
undef if there's something wrong
=cut
sub check_param {
my $self = shift;
my $arg_h = shift;
if (! defined $arg_h->{param} || ! exists $arg_h->{value})
{ $self->debugme("Missing param or value for check_and_add_param\nParam: ".($arg_h->{param} || undef)."; value: ".($arg_h->{value} || undef));
return undef;
}
if (!$self->is_valid_param_p($arg_h->{param}))
{ $self->debugme($arg_h->{param} . " is not a valid param!");
return undef;
}
# check if it passes the test
# get the DFV profile for the param
my $dfv_prof = $self->dfv_profile([ $arg_h->{param} ]);
## validate the input hash data
my $results = Data::FormValidator->check( { $arg_h->{param} => $arg_h->{value} }, $dfv_prof );
return 1 if $results->success;
if (!$results->success) {
# there was a problem in the input hash
# summarize the errors
my $err_arr = GO::Utilities::summarize_errors($results);
$self->debugme("new param " . $arg_h->{param} . " failed the test:\n" . join("\n", @$err_arr)."\n");
$self->warning_msg( join '
', @$err_arr );
}
return undef;
}
=cut
sub add_Object {
my $self = shift;
my $arg_h = shift || return;
my $obj_type = $arg_h->{obj_type} || return;
my $data = $arg_h->{data} || return;
# check that we have
$self->debugme("object type: $obj_type");
eval "require $obj_type";
if ($@) {
$self->debugme("error: ".Dumper($@));
# $self->printerr("$obj_type is not installed!");
$self->add_error( { CLASS => 'fatal', MSG => "$obj_type is not installed on this system.", CALLER => (caller(0))[3] } );
return;
}
my $results = $obj_type->new_object_from_parsed_data($arg_h);
if ($results->{OBJECT})
{ my $add_as = $arg_h->{add_as};
if (!$add_as)
{ ($add_as = $obj_type) =~ s/.*::(.+)/\L$1/;
}
$self->{$add_as} = $results->{OBJECT};
}
if ($results->{ERROR_LIST})
{ $self->
}
=cut
=head2 obj_to_text
Converts an object into the textual representation thereof
=cut
sub obj_to_text {
my $self = shift;
$self->startme();
my $arg_h = shift; #
my $valid_params = $self->get_valid_params;
$self->debugme("valid params: ".join(", ", @$valid_params));
my $str; # the string representing the data
# use either the data separator set in 'arg_h' or \n
my $data_sep = $arg_h->{data_sep} || "\n";
# tag-value separator
my $tag_val_sep = $arg_h->{tag_val_sep} || ":";
foreach my $p (@$valid_params)
{ $self->debugme("param $p");
if ($self->$p)
{ $self->debugme("found value for self->$p: it is ".Dumper($self->$p));
my $param_spec = $self->get_spec_for_param($p);
if ($param_spec->{allow_multiple} && $param_spec->{allow_multiple} == 1)
{
# $str .= $p . $tag_val_sep . " " . Dumper($self->$p) . $data_sep;
foreach ( @{$self->$p} )
{ $str .= $p . $tag_val_sep . " " . $_ . $data_sep;
}
}
else
{ $str .= $p . $tag_val_sep . " " . $self->$p . $data_sep;
}
}
else
{ $self->debugme("Can't do self->" . $p . "... crap!");
}
}
return $str;
}
# auto-declare accessors
sub AUTOLOAD {
my $self = shift;
my $name = $AUTOLOAD;
return if $name =~ /DESTROY/;
$name =~ s/^.*:://; # strip fully-qualified portion
my $do_this;
my $arg_h;
if ($name =~ /^(add|set)_.+/)
{ $do_this = $1;
$name =~ s/^(add|set)_//;
}
# check if the name is a valid param or not
if ($self->is_valid_param_p($name))
{ # phew!
}
elsif ($self->is_valid_param_p($name."_list"))
{ # add "_list" to it
$name.= "_list";
}
else
{ # holy crap! What's going on here?!
# this isn't a valid param
$self->printerr("Can't do $name on self");
return;
}
if ($do_this)
{ # create the args for adding a parameter
# obviously we need an argument...
return unless @_;
# this is our argument so far
my $input = shift;
if (ref($input) eq 'HASH')
{ # proper format. Phew!
%$arg_h = %$input;
}
else
{ # input is something else
# assume it is the value
$arg_h->{value} = $input;
}
# send the argument to add_param
$self->debugme("sending $name to add_param");
$arg_h->{param} = $name;
$arg_h->{add} = 1 if $do_this eq 'add';
return $self->set_param($arg_h);
}
else
{ return $self->{$name};
}
# confess("$self") unless ref($self);
# my $add;
# if ($name =~ /add_(.+)/) {
# $add = $1."_list";
# }
# if ($self->can($name)) {
# confess("assertion error!");
# }
# if ($add && $self->is_valid_param_p($add)) {
# push(@{$self->{$add}}, @_);
# return $self->{$add};
# }
# else {
# confess("can't do $name on $self");
# }
}
1;