File Coverage

blib/lib/WebService/E4SE.pm
Criterion Covered Total %
statement 25 27 92.5
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 34 36 94.4


line stmt bran cond sub pod time code
1             package WebService::E4SE;
2              
3 1     1   115580 use Moo;
  1         5854  
  1         5  
4 1     1   2039 use Authen::NTLM 1.09;
  1         11751  
  1         99  
5 1     1   10 use LWP::UserAgent 6.02;
  1         30  
  1         29  
6 1     1   5 use HTTP::Headers;
  1         2  
  1         34  
7 1     1   4 use HTTP::Request;
  1         2  
  1         22  
8 1     1   5 use Scalar::Util ();
  1         2  
  1         20  
9 1     1   5 use URI 1.60;
  1         17  
  1         22  
10 1     1   628 use XML::Compile::Licensed ();
  1         379  
  1         22  
11 1     1   625 use XML::Compile::SOAP11 ();
  0            
  0            
12             use XML::Compile::SOAP12 ();
13             use XML::Compile::SOAP11::Client ();
14             use XML::Compile::WSDL11 ();
15             use XML::Compile::Transport::SOAPHTTP ();
16             use XML::LibXML ();
17              
18             use Carp ();
19             use strictures 2;
20             use namespace::clean;
21             use v5.10;
22              
23             our $AUTHORITY = 'cpan:CAPOEIRAB';
24             our $VERSION = '0.050';
25             $VERSION = eval $VERSION;
26              
27             has _ua => (
28             is => 'rw',
29             lazy => 1,
30             isa => sub {
31             die "Not an LWP::UserAgent"
32             unless Scalar::Util::blessed($_[0]) && $_[0]->isa('LWP::UserAgent');
33             },
34             default => sub { LWP::UserAgent->new(keep_alive => 1) },
35             );
36              
37             has base_url => (
38             is => 'rw',
39             isa => sub {
40             die "Not an URI"
41             unless Scalar::Util::blessed($_[0]) && $_[0]->isa('URI');
42             },
43             coerce => sub {
44             (Scalar::Util::blessed($_[0]) && $_[0]->isa('URI'))
45             ? $_[0]
46             : URI->new($_[0]);
47             },
48             required => 1,
49             default => sub {
50             URI->new('http://epicor/e4se/');
51             }
52             );
53              
54             has files => (
55             is => 'rw',
56             lazy => 1,
57             isa => sub {
58             die 'Should be an array reference'
59             unless $_[0] && ref($_[0]) eq 'ARRAY';
60             },
61             default => sub {
62             [
63             qw(
64             ActionCalls Attachment BackOfficeAP BackOfficeAR BackOfficeCFG
65             BackOfficeGBBackOfficeGL BackOfficeIV BackOfficeMC Billing
66             Business Carrier CommercialTerms Company ControllingProject
67             CostVersion CRMClientHelper Currency Customer ECSClientHelper
68             Employee ExchangeInterface Expense FinancialsAP FinancialsAR
69             FinancialsCFG FinancialsGB FinancialsGL FinancialsMC
70             FinancialsSync GLAccount IntersiteOrder InventoryLocation Journal
71             Location LotSerial Manufacturer Material MaterialPlan MiscItems
72             MSProject MSProjectEnterpriseCustomFieldsandLookupTables
73             Opportunity Organization PartMaster Partner PriceStructure
74             Project Prospect PSAClientHelper PurchaseOrder Receiving Recognize
75             Resource SalesCycleManagement SalesOrder SalesPerson Shipping Site
76             Supplier svActionCall svAttachment svBackOfficeAP svBackOfficeAR
77             svBackOfficeCFG svBackOfficeGB svBackOfficeGL svBackOfficeIV
78             svBackOfficeMC svBilling svBusiness svCarrier svCommercialTerms
79             svCompany svControllingProject svCostVersion svCRMClientHelper
80             svCurrency svCustomer svECSClientHelper svEmployee
81             svExchangeInterface svExpense svFinancialsAP svFinancialsAR
82             svFinancialsCFG svFinancialsGB svFinancialsGL svFinancialsMC
83             svFinancialsSync svGLAccount svIntersiteOrder svInventoryLocation
84             svJournal svLocation svLotSerial svManufacturer svMaterial
85             svMaterialPlan svMiscItems svMSProject
86             svMSProjectEnterpriseCustomFieldsandLookupTables svOpportunity
87             svOrganization svPartMaster svPartner svPriceStructure svProject
88             svProspect svPSAClientHelper svPurchaseOrder svReceiving
89             svRecognize svResource svSalesCycleManagement svSalesOrder
90             svSalesPerson svShipping svSite svSupplier svSysArtifact
91             svSysDirector svSysDomainInfo svSysNotify svSysSearchManager
92             svSysSecurity svSysWorkflow svTax svTime svUOM SysArtifact
93             SysDirector SysDomainInfo SysNotify SysSearchManager SysSecurity
94             SysWorkflow Tax Time UOM
95             )
96             ];
97             },
98             );
99              
100             has force_wsdl_reload => (
101             is => 'rw',
102             coerce => sub {
103             return 0 unless $_[0];
104             return 1 if ref($_[0]);
105             (lc($_[0]) eq 'false') ? 0 : 1;
106             },
107             required => 1,
108             default => 0
109             );
110              
111             has password => (is => 'rw', required => 1, default => '',);
112              
113             has realm => (is => 'rw', required => 1, default => '',);
114              
115             has site => (is => 'rw', required => 1, default => 'epicor:80',);
116              
117             has username => (is => 'rw', required => 1, default => '',);
118              
119             sub _get_port {
120             my ($self, $file) = @_;
121             return "WSSoap" unless defined($file) and length($file);
122             $file =~ s/\.\w+$//; #strip extension
123             return $file . "WSSoap";
124             }
125              
126             sub _valid_file {
127             my ($self, $file) = @_;
128             return 0 unless defined $file and length $file;
129             $file =~ s/\.asmx$//i;
130             return 1 if (grep { $_ eq $file } @{$self->files});
131             return 0;
132             }
133              
134             sub call {
135             my ($self, $file, $function, %parameters) = @_;
136             Carp::croak("$file is not a valid web service found in E4SE.")
137             unless $self->_valid_file($file);
138              
139             my $wsdl = $self->get_object($file);
140             Carp::croak("Couldn't obtain the WSDL") unless $wsdl;
141              
142             return $wsdl->call($function, %parameters);
143             }
144              
145             sub get_object {
146             my ($self, $file) = @_;
147             $self->{cache} //= {};
148             Carp::croak("$file is not a valid web service found in E4SE.")
149             unless $self->_valid_file($file);
150             my $cache = $self->{cache};
151             if ($self->force_wsdl_reload()) {
152             delete($cache->{$file});
153             $self->force_wsdl_reload(0);
154             }
155              
156             #if our wsdl is already setup, let's just return
157             return $cache->{$file}
158             if (exists($cache->{$file}) && defined($cache->{$file}));
159              
160             #wsdl doesn't exist. let's setup the user agent for our transport and move along
161             $self->_ua->credentials($self->site, $self->realm, $self->username,
162             $self->password);
163              
164             my $res = $self->_ua->get(URI->new("$file?WSDL")->abs($self->base_url));
165             Carp::croak("Unable to grab the WSDL from $file: " . $res->status_line())
166             unless $res->is_success;
167              
168             $cache->{$file} = XML::Compile::WSDL11->new($res->decoded_content,
169             server_type => 'SharePoint');
170             Carp::croak("Unable to create new XML::Compile::WSDL11 object")
171             unless $cache->{$file};
172              
173             my $trans = XML::Compile::Transport::SOAPHTTP->new(
174             user_agent => $self->_ua,
175             address => URI->new($file)->abs($self->base_url),
176             );
177             Carp::carp("Unable to create new XML::Compile::Transport::SOAPHTTP object")
178             unless $trans;
179              
180             $cache->{$file}
181             ->compileCalls(port => $self->_get_port($file), transport => $trans,);
182             return $cache->{$file};
183             }
184              
185             sub operations {
186             my ($self, $file) = @_;
187             Carp::croak("$file is not a valid web service found in E4SE.")
188             unless $self->_valid_file($file);
189             my $wsdl = $self->get_object($file);
190             my @ops = $wsdl->operations(port => $self->_get_port($file));
191             return [map { $_->name } @ops];
192             }
193              
194             1; # End of WebService::E4SE
195              
196             =encoding utf8
197              
198             =head1 NAME
199              
200             WebService::E4SE - Communicate with the various Epicor E4SE web services.
201              
202             =head1 SYNOPSIS
203              
204             use WebService::E4SE;
205              
206             # create a new object
207             my $ws = WebService::E4SE->new(
208             username => 'AD\username', # NTLM authentication
209             password => 'A password', # NTLM authentication
210             realm => '', # LWP::UserAgent and Authen::NTLM
211             site => 'epicor:80', # LWP::UserAgent and Authen::NTLM
212             base_url => URL->new('http://epicor/e4se'), # LWP::UserAgent and Authen::NTLM
213             timeout => 30, # LWP::UserAgent
214             );
215              
216             # get an array ref of web service APIs to communicate with
217             my $res = $ws->files();
218             say Dumper $res;
219              
220             # returns a list of method names for the file you wanted to know about.
221             my @operations = $ws->operations('Resource');
222             say Dumper @operations;
223              
224             # call a method and pass some named parameters to it
225             my ($res,$trace) = $ws->call('Resource','GetResourceForUserID', userID=>'someuser');
226              
227             # give me the XML::Compile::WSDL11 object
228             my $wsdl = $ws->get_object('Resource'); #returns the usable XML::Compile::WSDL11 object
229              
230             =head1 DESCRIPTION
231              
232             L allows us to connect to
233             L SOAP-based APIs
234             service to access our data or put in our timesheet.
235              
236             Each action on the software calls a SOAP-based web service API method. Each API
237             call is authenticated via NTLM.
238              
239             There are more than 100 web service files you could work with (.asmx
240             extensions) each having their own set of methods. On your installation of E4SE,
241             you can get a listing of method calls available by visiting one of those files
242             directly (C for example).
243              
244             The module will grab the WSDL from the file you're trying to deal with. It will make
245             use of that WSDL with L. You can force a reload of the WSDL at any
246             time. So, we build the L object and hold onto it for any further
247             calls to that file. These are generated by the calls you make, so hopefully we don't
248             kill you with too many objects. You can work directly with the new L
249             object if you like, or use the abstracted out methods listed below.
250              
251             For transportation, we're using L using
252             L with L.
253              
254             =head1 ATTRIBUTES
255              
256             L makes the following attributes available:
257              
258             =head2 base_url
259              
260             my $url = $ws->base_url;
261             $url = $ws->base_url(URI->new('http://epicor/e4se'));
262              
263             This should be the base L for your E4SE installation.
264              
265             =head2 files
266              
267             my $files = $ws->files;
268             $files = $ws->files(['file1', 'file2']);
269             say join ', ', @$files;
270              
271             This is reference to an array of file names that this web service has
272             knowledge of for an E4SE installation. If your installation has some services
273             that we're missing, you can inject them here. This will clobber, not
274             merge/append.
275              
276             =head2 force_wsdl_reload
277              
278             my $force = $ws->force_wsdl_reload;
279             $force = $ws->force_wsdl_reload(1);
280              
281             This attribute is defaulted to false (0). If set to true, the next call to a
282             method that would require a L object will go out to the
283             server and re-grab the WSDL and re-setup that WSDL object no matter if we have
284             already generated it or not. The attribute will be reset to false (0) directly
285             after the next WSDL object setup.
286              
287             =head2 password
288              
289             my $pass = $ws->password;
290             $pass = $ws->password('foobarbaz');
291              
292             This will be your domain password. No attempt to hide this is made.
293              
294             =head2 realm
295              
296             my $realm = $ws->realm;
297             $realm = $ws->realm('MyADRealm');
298              
299             Default is an empty string. This is for the L module and can generally be left blank.
300              
301             =head2 site
302              
303             my $site = $ws->site;
304             $site = $ws->site('epicor:80');
305              
306             This is for the L module. Set this accordingly.
307              
308             =head2 username
309              
310             my $user = $ws->username;
311             $user = $ws->username('AD\myusername');
312              
313             Usually, you need to prefix this with the domain your E4SE installation is using.
314              
315             =head1 METHODS
316              
317             L makes the following methods available:
318              
319             =head2 call
320              
321             use Try::Tiny;
322             try {
323             my ( $res, $trace) = $ws->call('Resource', 'GetResourceForUserID', %parameters );
324             say Dumper $res;
325             }
326             catch {
327             warn "An error happened: $_";
328             exit(1);
329             }
330              
331             This method will call an API method for the file you want. It will die on
332             errors outside of L's knowledge, otherwise
333             it's just a little wrapper around L->call();
334              
335             Another way to do this would be
336              
337             $ws->get_object('Reource')->call( 'GetResourceForUserID', %params );
338              
339             =head2 get_object
340              
341             my $wsdl = $ws->get_object('Resource');
342              
343             This method will return an L object for the file name
344             you supply. This handles going to the file's WSDL URL, grabbing that URL
345             with L and L, and using that WSDL response to
346             setup a new L object.
347              
348             Note that if you have previously setup a L object for that
349             file name, it will just return that object rather than going to the server and
350             requesting a new WSDL.
351              
352             =head2 operations
353              
354             my $available_operations = $ws->operations( $file );
355              
356             This method will return a list of L objects
357             that are available for the given file.
358              
359             =head1 AUTHOR
360              
361             Chase Whitener << >>
362              
363             =head1 BUGS
364              
365             Please report any bugs or feature requests on GitHub L.
366             We appreciate any and all criticism, bug reports, enhancements, or fixes.
367              
368             =head1 SUPPORT
369              
370             You can find documentation for this module with the perldoc command.
371              
372             perldoc WebService::E4SE
373              
374              
375             You can also look for information at:
376              
377             =over 4
378              
379             =item * GitHub
380              
381             L
382              
383             =back
384              
385             =head1 LICENSE AND COPYRIGHT
386              
387             Copyright 2013
388              
389             This is free software, licensed under:
390              
391             The Artistic License 2.0 (GPL Compatible)
392              
393             =cut