-
Notifications
You must be signed in to change notification settings - Fork 34
/
Toolkit.php
2472 lines (2142 loc) · 89.6 KB
/
Toolkit.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
namespace ToolkitApi;
use PDO;
include_once __DIR__ . DIRECTORY_SEPARATOR . 'ToolkitServiceSet.php';
include_once __DIR__ . DIRECTORY_SEPARATOR . 'ToolkitService.php';
if (!defined('CONFIG_FILE')) {
define('CONFIG_FILE', 'toolkit.ini');
}
/**
* Class Toolkit
*
* @package ToolkitApi
*/
class Toolkit implements ToolkitInterface
{
const VERSION = "1.8.5"; // version number for front-end PHP toolkit
/* @todo use inputXml and outputXml to make this class more flexibly OO-like. Fewer strings copied around.
* Better would be to use a Request object that has a connection.
* There could be multiple Request objects even if only one connection.
* Each request object could have its own input and output.
*/
protected $XMLWrapper = null;
protected $conn = NULL; // connection to database or other transport
// @todo create methods to set (separately and all at once) and get errors, akin to what was done for DB module.
// Test ability to retrieve program errors and text.
protected $error = ''; // ambiguous usage (it's here for backward compat.) but mainly 7-char error code
protected $cpfErr = ''; // 7-char error code
protected $errorText = ''; // error text/msg
// $db2 variable may not be needed. Consider deprecating in future.
protected $db2 = false;
/**
* @var null|resource|PDO|PdoSupp|odbcsupp|db2supp
*/
protected $db = null; // contains class for db connections
protected $_i5NamingFlag = 0; // same as DB2_I5_NAMING_OFF; // Other value could be 1 (DB2_I5_NAMING_ON).
protected $_schemaSep = '.'; // schema separator. A dot or slash
protected $_validSeparators = array('.', '/');
protected $_outputVarsToExport = array();
protected $_isPersistent = false;
protected $_transport;
protected $_isCw = false; // whether the CW is used for this instance.
static protected $_config; // config options from INI file. Accessed by static method getConfigValue();
static protected $_os; // operating system
// brought over from CW
protected $joblog = '';
protected $_privateConnNum = null;
protected $_isNewConn = true; // default to "new/true" because it's safest. new means app perhaps should initalize job.
// In case of error, look for CPFs generated by specific other programs.
// E.g. if userid is wrong when calling QSYGETPH, the CPF may be reported by program QSYPHDL in joblog.
// @todo would be better specified in external file or in INI.
protected $_cpfMapping = array('QSYRUSRI' => array('QLIROHDL'), 'QSYGETPH' => array('QSYPHDL'));
// options that can be set at any time
// Set them with setOptions or setToolkitServiceParams
// Get them with getOption or getToolkitServiceParam
protected $_options = array('plug' => 'iPLUG512K', //for ibm_db2. for odbc $plug='iPLUGR512K'. consider 4K with small data
'plugSize' => '512K', // 4K, 32K, 512K, 65K, 512K, 1M, 5M, 10M, 15M
'plugPrefix' => 'iPLUG', // iPLUG (ibm_db2) or iPLUGR (odbc)
'XMLServiceLib' => XMLSERVICELIB, // library containing XMLSERVICE
'HelperLib' => ZSTOOLKITLIB, // library containing Zend Server's RPG service program (ZSXMLSRV by default) that handles some of the toolkit's duties.
'v5r4' => false, // whether to ask XMLSERVICE to carefully use features that v5r4 can handle
'sbmjobParams' => '', // XMLSERVICE itself will provide good defaults in most cases (in production ZENDSVR(6)/ZSVR_JOBD/XTOOLKIT. In test mode, QSYS/QSRVJOB/XTOOLKIT). See PLUGCONF1 and 2
'debug' => false,
'debugLogFile' => '/usr/local/zendsvr6/var/log/debug.log',
// CCSID/Hex at a global/request level. These properties are also defined at a parameter object level.
'ccsidBefore' => '',
'ccsidAfter' => '',
'useHex' => false,
'paseCcsid' => '', // PASE CCSID for <sh> pase such as WRKACTJOB
'trace' => false, // whether to enable XMLSERVICE internal log
'sbmjobCommand' => '', // optional complete override of SBMJOB command when new toolkit job is submitted
'prestart' => false,
'stateless' => false,
'performance' => false, // whether to enable performance collection (not fully implemented)
'idleTimeout' => '', // created for Compat. Wrapper (CW)
'cdata' => true, // whether to ask XMLSERVICE to wrap its output in CDATA to protect reserved XML characters
'internalKey' => XMLINTERNALKEY, // consistent naming style
'encoding' => "ISO-8859-1", /*English. Hebrew:ISO-8859-8 */
'schemaSep' => '.', // schema separator. A dot or slash
'parseOnly' => false, // do not run any program; simply parse, expand dim if necessary, and return.
'parseDebugLevel' => 1, // 1-9 debug level when in parseOnly mode
'license' => false, // true to receive license/version information
'transport' => false, // check proc call speed (no XML calls)
'dataStructureIntegrity' => false, // New in 1.4.0. Specify true to preserve integrity of data structures. If false (default), explode inner values out of the ds.
'arrayIntegrity' => false, // New in 1.4.0. Specify true to preserve integrity of arrays (to create true named arrays, not merely sequentially numbered elements).
'customControl' => '', // any string you want. Will be concatenated to control key string after a single space.
'transportType' => 'ibm_db2', // can override in getInstance constructor as well
'httpTransportUrl' => '', // for HTTP REST transport
'timeReport' => false, // *fly or *nofly; if true, return tick counts instead of data.
);
// plug size to bytes cross-reference
protected $_dataSize = array("4K" => 4096,
"32K" => 32000,
"65K" => 65000,
"512K" => 512000,
"1M" => 1000000,
"5M" => 5000000,
"10M" => 10000000,
"15M" => 15000000);
protected $serviceParams;
protected $optionalParamNames;
protected $execStartTime;
/**
* Return true if we are in debug mode
* Return false if not
*
* Usage:
* $isDebug= $this->isDebug();
*
* @return boolean
*/
protected function isDebug()
{
return $this->getOption('debug');
}
/**
* if passing an existing resource and naming, don't need the other params.
*
* @param string|resource $databaseNameOrResource
* @param string $userOrI5NamingFlag 0 = DB2_I5_NAMING_OFF or 1 = DB2_I5_NAMING_ON
* @param string $password
* @param string $transportType (http, ibm_db2, odbc)
* @param bool $isPersistent
* @throws \Exception
*/
public function __construct($databaseNameOrResource, $userOrI5NamingFlag = '0', $password = '', $transportType = '', $isPersistent = false)
{
$this->execStartTime = '';
// stop any types that are not valid for first parameter. Invalid values may cause toolkit to try to create another database connection.
if (!is_string($databaseNameOrResource) && !is_resource($databaseNameOrResource) && ((!is_object($databaseNameOrResource) || (is_object($databaseNameOrResource) && get_class($databaseNameOrResource) !== PDO::class)))) {
// initialize generic message
$this->error = "\nFailed to connect. databaseNameOrResource " . var_export($databaseNameOrResource, true) . " not valid.";
// change to a more specific helpful message if a boolean false arising from a failed database connection passed in
if (false === $databaseNameOrResource) {
$this->error = "\nFailed to connect. If you passed in a database connection, it was a failed one with a value of false.";
}
$this->debugLog($this->error);
throw new \Exception($this->error);
} // (if (!is_string....)
// set service parameters to use in object.
$this->serviceParams = $this->getDefaultServiceParams();
if ($this->getConfigValue('system', 'sbmjob_params')) {
$this->serviceParams['sbmjobParams'] = $this->getConfigValue('system', 'sbmjob_params');
}
// Optional params. Don't specify if not given in INI.
$this->getOptionalParams('system', array('v5r4', 'ccsidBefore', 'ccsidAfter', 'useHex', 'paseCcsid', 'trace', 'dataStructureIntegrity', 'arrayIntegrity'));
$this->getOptionalParams('transport', array('httpTransportUrl', 'plugSize'));
// populate serviceParams with $transport, or get it from INI
if (!$transportType) {
$this->getOptionalParams('transport', array('transportType'));
} else {
$this->serviceParams['transportType'] = $transportType;
}
// set up options in this object. Includes debugging, logging, transport.
$this->setOptions($this->serviceParams);
// get transport type from options, wherever it came from, and lowercase it.
$transportType = $this->getOption('transportType');
if (is_string($transportType)) {
$transportType = strtolower($transportType);
}
if ($this->isDebug()) {
$this->debugLog("Creating new conn with database: '$databaseNameOrResource', user or i5 naming flag: '$userOrI5NamingFlag', transport: '$transportType', persistence: '$isPersistent'\n");
}
// do we have a DB resource "by user" or do we create one
if ($transportType === 'ibm_db2' && is_resource($databaseNameOrResource)) {
$conn = $databaseNameOrResource;
$this->_i5NamingFlag = $userOrI5NamingFlag;
$schemaSep = ($this->_i5NamingFlag) ? '/' : '.';
$this->setOptions(array('schemaSep' => $schemaSep));
$this->chooseTransport('ibm_db2');
if ($this->isDebug()) {
$this->debugLog("Re-using existing db connection with schema separator: $schemaSep");
}
} elseif ($transportType === 'odbc' && is_resource($databaseNameOrResource)) {
$conn = $databaseNameOrResource;
$this->_i5NamingFlag = $userOrI5NamingFlag;
$schemaSep = ($this->_i5NamingFlag) ? '/' : '.';
$this->setOptions(array('schemaSep' => $schemaSep));
$this->chooseTransport('odbc');
if ($this->isDebug()) {
$this->debugLog("Re-using existing db connection with schema separator: $schemaSep");
}
} elseif ($databaseNameOrResource instanceof PDO && $transportType === 'pdo') {
$conn = $databaseNameOrResource;
$this->db = $conn;
$this->_i5NamingFlag = $userOrI5NamingFlag;
$schemaSep = ($this->_i5NamingFlag) ? '/' : '.';
$this->setOptions(array('schemaSep' => $schemaSep));
$this->chooseTransport('pdo');
if ($this->isDebug()) {
$this->debugLog("Re-using existing db connection with schema separator: $schemaSep");
}
} elseif ($transportType === 'http' || $transportType === 'https') {
$databaseName = $databaseNameOrResource;
$user = $userOrI5NamingFlag;
$this->chooseTransport($transportType);
$transport = $this->getTransport();
$conn = $transport->connect($databaseName, $user, $password, array('persistent'=>$this->getIsPersistent()));
} else {
$databaseName = $databaseNameOrResource;
$user = $userOrI5NamingFlag;
if ($this->isDebug()) {
$this->debugLog("Creating a new db connection at " . date("Y-m-d H:i:s") . ".\n");
$this->execStartTime = microtime(true);
}
$this->setIsPersistent($isPersistent);
$this->chooseTransport($transportType);
$transport = $this->getTransport();
$conn = $transport->connect($databaseName, $user, $password, array('persistent'=>$this->getIsPersistent()));
if ($this->isDebug()) {
$durationCreate = sprintf('%f', microtime(true) - $this->execStartTime);
$this->debugLog("Created a new db connection in $durationCreate seconds.");
}
if (!$conn) {
// Note: SQLState 08001 (with or without SQLCODE=-30082) usually means invalid user or password. This is true for DB2 and ODBC.
$sqlState = $transport->getErrorCode();
$this->error = $transport->getErrorMsg();
$this->debugLog("\nFailed to connect. sqlState: $sqlState. error: $this->error");
throw new \Exception($this->error, (int)$sqlState);
}
}
$this->conn = $conn;
}
/**
* @throws \Exception
*/
public function __clone()
{
throw new \Exception(" Use getInstance() function according to create a new ToolkitService object");
}
/**
* get service param values from Config to use in object
*
*/
protected function getDefaultServiceParams()
{
return array('XMLServiceLib' => $this->getConfigValue('system', 'XMLServiceLib', 'ZENDSVR6'),
'HelperLib' => $this->getConfigValue('system', 'HelperLib', 'ZENDSVR6'),
'debug' => $this->getConfigValue('system', 'debug', false),
'debugLogFile' => $this->getConfigValue('system', 'debugLogFile', false),
'encoding' => $this->getConfigValue('system', 'encoding', 'ISO-8859-1'),
'parseOnly' => $this->getConfigValue('testing', 'parse_only', false),
'parseDebugLevel' => $this->getConfigValue('testing', 'parse_debug_level', null));
}
/**
* get optional param values from Config and add them to the service params
*
* @param $type
* @param array $optionalParams
*/
protected function getOptionalParams($type, array $optionalParams)
{
foreach ($optionalParams as $optionalParamName) {
$val = $this->getConfigValue($type, $optionalParamName);
if ($val) {
$this->serviceParams[$optionalParamName] = $val;
}
$this->optionalParamNames = $optionalParamName;
}
}
/**
* whether we're using CW (compatibility wrapper) or not
*
* @param $isCw
*/
public function setIsCw($isCw)
{
$this->_isCw = $isCw;
}
/**
* @return bool
*/
public function getIsCw()
{
return $this->_isCw;
}
/**
* return array of valid plug sizes.
* public method in case an application wishes to validate.
*
* @return array
*/
public function validPlugSizes()
{
return array_keys($this->_dataSize);
}
/**
* valid plug sizes separated by commas. Useful for informational messages.
*
* @return string
*/
protected function validPlugSizeList()
{
return implode($this->validPlugSizes(), ', ');
}
/**
* return size in bytes based on plugSize.
*
* @param $plugSize
* @throws \Exception
*/
protected function plugSizeToBytes($plugSize)
{
// return size in bytes based on plugSize.
if (isset($this->_dataSize[$plugSize])) {
return $this->_dataSize[$plugSize];
}
throw new \Exception("plugSize '$plugSize' is not valid. Try one of these: " . $this->validPlugSizeList());
}
/**
* @param $transportObject
*/
protected function setTransport($transportObject)
{
$this->_transport = $transportObject;
}
/**
* @return mixed
*/
protected function getTransport()
{
return $this->_transport;
}
/**
* Choose data transport type: ibm_db2, odbc, http
*
* @param string $transportName 'ibm_db2' or 'odbc' or 'http'
* @throws \Exception
*/
protected function chooseTransport($transportName = '')
{
switch($transportName)
{
case 'http':
$transport = new httpsupp();
$transport->setUrl(
$this->getOption('httpTransportUrl')
);
$this->setTransport($transport);
break;
case 'https':
$transport = new httpsupp();
// Set SSL certificate authority file
$sslCaFile = $this->getConfigValue('transport', 'sslCaFile');
$transport->setSSLCAFile($sslCaFile);
// Set server name
$serverName = $this->getConfigValue('transport', 'serverName');
$transport->setServerName($serverName);
$transport->setUrl(
$this->getOption('httpTransportUrl')
);
$this->setTransport($transport);
break;
default:
$this->setDb($transportName);
}
}
/**
* transport type is same as db extension name when a db transport is used.
*
* @param string $transportType
* @throws \Exception
*/
protected function setDb($transportType = '')
{
$transportType = trim($transportType);
// if extension is specified, use it; else use default db.
$extensionName = ($transportType) ? $transportType : DBPROTOCOL;
if (!extension_loaded($extensionName)) {
throw new \Exception("Extension $extensionName not loaded.");
}
// extension is loaded. Set up db transport objects.
if ($extensionName === 'ibm_db2') {
$this->setOptions(array('plugPrefix' => 'iPLUG'));
$this->db = new db2supp();
$this->setDb2(); // not used in toolkit anymore but keep for backwards compat.
} elseif ($extensionName === 'odbc') {
//for odbc will be different default stored procedure call
$this->setOptions(array('plugPrefix' => 'iPLUGR')); // "R" = "result set" which is how ODBC driver returns param results
$this->db = new odbcsupp();
} elseif ($extensionName === 'pdo') {
$this->setOptions(array('plugPrefix' => 'iPLUGR'));
$this->db = new PdoSupp($this->db);
}
// transport, too, to be generic
$this->setTransport($this->db);
return;
}
/**
* Also alias setOptions()
*
* @param array $XmlServiceOptions
*/
public function setToolkitServiceParams(array $XmlServiceOptions)
{
// copy incoming options to new array that we can safely manipulate
$options = $XmlServiceOptions;
// special cases first
/* If sbmjobParams is present, it must contain at least one slash. If not, do not process it.
* The slash implies that subsystem name and subsytem decscription (and, optionally, job name)
* are present in the string
*/
if (isset($options['sbmjobParams']) && !strstr($options['sbmjobParams'], "/")) {
unset($options['sbmjobParams']);
} //(if sbmjobParams is set but no slash)
// if a plug name is passed in, it overrides plugPrefix and plugSize.
if (isset($options['plug']) && $options['plug']) {
// @todo enumerate plug prefixes centrally, tied to db extension name, at top of this class
$possiblePrefixes = array('iPLUG', 'iPLUGR');
$options['plugSize'] = str_replace($possiblePrefixes, '', $options['plug']); // remove prefix to get size
$options['plugPrefix'] = str_replace($options['plugSize'], '', $options['plug']); // remove size to get prefix
}
// encoding provided but it's blank
if (isset($options['encoding']) && !$options['encoding']) {
unset($options['encoding']);
}
// verify that schemaSep is a valid character for this purpose
if (isset($options['schemaSep']) && !in_array($options['schemaSep'], $this->_validSeparators)) {
unset($options['schemaSep']);
}
// handle case sensitivity. Put value in index of proper camel casing
if (isset($options['InternalKey'])) {
$options['internalKey'] = $options['InternalKey'];
}
// now set all in a generic fashion
// loop through all options provided in param
foreach ($options as $optionName=>$optionValue) {
if (isset($this->_options[$optionName])) {
// it's valid. Set class property to its value.
$this->_options[$optionName] = $optionValue;
}
}
}
/**
* shorthand for getToolkitServiceParam()
*
* @param $optionName
* @return bool|void
* @throws \Exception
*/
public function getOption($optionName)
{
$result = $this->getToolkitServiceParam($optionName);
if ($result) {
return $result;
}
return false;
}
/**
* retrieve full toolkit option array
*
* @return array
*/
public function getOptions()
{
return $this->_options;
}
/**
* shorthand for setToolkitServiceParams()
*
* @param array $options
*/
public function setOptions($options = array())
{
$this->setToolkitServiceParams($options);
}
/**
* get a single option value
* return property value if property is set.
*
* @param $optionName
* @return bool
* @throws \Exception
*/
public function getToolkitServiceParam($optionName)
{
// special case, case sensitivity
if ($optionName == 'InternalKey') {
$optionName = 'internalKey';
}
// we use array_key_exists() rather than isset() because the value may be null.
if (array_key_exists($optionName, $this->_options)) {
return $this->_options[$optionName];
} else {
// nothing matched
Throw new \Exception("Invalid option requested: $optionName");
}
}
/**
* end job if private job (internal key set); end DB transport if not persistent.
*/
public function disconnect()
{
// if stateful connection, end the toolkit job.
if (!$this->isStateless()) {
$this->PgmCall("OFF", NULL);
}
// if transport is a db, end the db connection.
if (isset($this->db) && $this->db) {
$this->db->disconnect($this->conn);
}
$this->conn = null;
}
/**
* same as disconnect but also really close persistent database connection.
*/
public function disconnectPersistent()
{
$this->PgmCall("OFF", NULL);
if (isset($this->db) && $this->db) {
$this->db->disconnectPersistent($this->conn);
}
$this->conn = null;
}
/**
* @param $stringToLog
*/
public function debugLog($stringToLog)
{
if ($this->isDebug()) {
error_log("$stringToLog", 3, $this->getOption('debugLogFile'));
}
}
/**
* isDb2 and setDb2 may not be needed. Deprecate in future.
*
* @return bool
*/
public function isDb2()
{
return $this->db2;
}
/**
* @return bool
*/
public function setDb2()
{
return $this->db2 = true;
}
/**
* for special requests such as transport, performance, license
*
* @param $callType
* @return array|bool
*/
public function specialCall($callType)
{
$this->setOptions(array($callType=>true));
$outputArray = $this->PgmCall("NONE", NULL, NULL ,NULL);
$this->setOptions(array($callType=>false));
return $outputArray;
}
/**
* @return array|bool
*/
public function callTransportOnly()
{
return $this->specialCall('transport');
}
/**
* @return array|bool
*/
public function performanceData()
{
return $this->specialCall('performance');
}
/**
* return license/version information
*
* @return array|bool
*/
public function licenseXMLSERVICE()
{
return $this->specialCall('license');
}
/**
* pgmCall
*
* @param string $pgmName Name of program to call, without library
* @param string $lib Library of program. Leave blank to use library list or current library
* @param null $inputParam An array of ProgramParameter objects OR XML representing params, to be sent as-is.
* @param null $returnParam ReturnValue Array of one parameter that's the return value parameter
* @param null $options Array of other options. The most popular is 'func' indicating the name of a subprocedure or function.
* @return array|bool
*/
public function pgmCall($pgmName, $lib, $inputParam = NULL, $returnParam = NULL, $options = NULL)
{
$this->cpfErr = '';
$this->error = '';
$this->joblog = '';
$function = NULL;
ProgramParameter::initializeFallbackVarName();
// If only one 'return' param, turn it into an array for later processing.
if ($returnParam instanceof ProgramParameter) {
$returnParam = array($returnParam);
}
$this->XMLWrapper = new XMLWrapper(array('encoding' => $this->getOption('encoding')), $this);
// $optional handles special requests such as 'license'
$disconnect = (strcmp($pgmName, "OFF") === 0) ? true : false;
$optional = (strcmp($pgmName, "NONE") === 0) ? true : false;
$outputParamArray = false;
if (isset($options['func'])) {
$function = $options['func'];
}
if ($disconnect || $optional) {
$inputXml = $this->XMLWrapper->disconnectXMLIn();
} else {
$inputXml = $this->XMLWrapper->buildXmlIn($inputParam, $returnParam, $pgmName, $lib, $function);
}
// send XML to XMLSERVICE
$outputXml = $this->sendXml($inputXml, $disconnect);
if ($outputXml != '') {
$outputParamArray = $this->XMLWrapper->getParamsFromXml($outputXml);
// didn't get expected return, search logs to find out why
if (!is_array($outputParamArray)) {
// No real data. Look for errors. Retrieve details from joblog.
$this->joblog = $this->XMLWrapper->getLastJoblog();
// standard list of programs that provide CPF codes in joblog
$programsToLookFor = array($pgmName, '< lveContext', '#mnrnrl', 'QRNXIE', '< allProgram');
if (isset($this->_cpfMapping[$pgmName])) {
// list of other programs not called directly that might generate CPF codes in joblog.
$programsToLookFor = array_merge($programsToLookFor, $this->_cpfMapping[$pgmName]);
}
// put values in $this->cpfErr and $this->error
$this->extractErrorFromJoblog($programsToLookFor);
}
}
unset ($this->XMLWrapper);
// output array includes in/out parameters and return parameters.
return $outputParamArray;
}
/**
* help people accustomed to outputting a resource as string and for testing
*
* @return string
*/
public function __toString()
{
$ipc = trim($this->getInternalKey());
$serviceLibrary = $this->getOption('XMLServiceLib');
$stringStart = "ToolkitService object using service library $serviceLibrary.";
$ipcInfo = ($ipc) ? "Internalkey: '$ipc'" : "Running stateless (inline, no IPC).";
return "$stringStart $ipcInfo";
}
/**
* @return string
*/
public function getErrorMsg() {
return $this->errorText;
}
/**
* @return string
*/
public function getErrorCode()
{
return $this->cpfErr;
}
/**
* @param $msg
*/
public function setErrorMsg($msg)
{
$this->errorText = $msg;
}
/**
* @param $code
*/
public function setErrorCode($code)
{
$this->cpfErr = $code;
}
/**
* @param array $OutputArray
* @return bool
*/
public function getOutputParam(array $OutputArray)
{
if (!is_array($OutputArray)) {
return false;
}
if (isset($OutputArray['io_param'])) {
return $OutputArray['io_param'];
}
return false;
}
/**
* Send any XML to XMLSERVICE toolkit. The XML doesn't have to represent a program.
* Was protected; made public to be usable by applications.
*
* @param $inputXml
* @param bool $disconnect
* @return string
* @throws \Exception
*/
public function ExecuteProgram($inputXml, $disconnect = false)
{
$this->execStartTime = '';
$this->error = '';
$this->VerifyPLUGName(); // calculates value of option 'plug'
$this->VerifyInternalKey();
// @todo create driver-specific SQL in driver classes (db2, odbc)
$internalKey = $this->getInternalKey();
$controlKeyString = $this->getControlKey($disconnect);
$plugSize = $this->getOption('plugSize');
// @todo have one transport class that includes db as well.
// If a database transport
if (isset($this->db) && $this->db) {
$result = $this->makeDbCall($internalKey, $plugSize, $controlKeyString, $inputXml, $disconnect);
} else {
// Not a DB transport. At this time, assume HTTP transport (which doesn't use a plug, by the way. uses outbytesize)
$transport = $this->getTransport();
$transport->setIpc($internalKey);
$transport->setCtl($controlKeyString);
$url = $this->getOption('httpTransportUrl');
$transport->setUrl($url);
// convert plugSize to bytes
$outByteSize = $this->plugSizeToBytes($plugSize);
// if debug mode, log control key, and input XML.
if ($this->isDebug()) {
$this->debugLog("\nExec start: " . date("Y-m-d H:i:s") . "\nVersion of toolkit front end: " . self::getFrontEndVersion() ."\nToolkit class: '" . __FILE__ . "'\nIPC: '" . $this->getInternalKey() . "'. Control key: $controlKeyString\nHost URL: $url\nExpected output size (plugSize): $plugSize or $outByteSize bytes\nInput XML: $inputXml\n");
$this->execStartTime = microtime(true);
}
$result = $transport->send($inputXml, $outByteSize);
}
// workaround: XMLSERVICE as of 1.7.4 returns a single space instead of empty string when no content was requested.
$result = trim($result);
if ($this->isDebug() && $result) {
$end = microtime(true);
$elapsed = $end - $this->execStartTime;
$this->debugLog("Output XML: $result\nExec end: " . date("Y-m-d H:i:s") . ". Seconds to execute: $elapsed.\n\n");
}
return $result;
}
/**
* @param string $internalKey
* @param string $plugSize
* @param string $controlKeyString
* @param string $inputXml
* @param bool $disconnect
* @return string
*/
protected function makeDbCall($internalKey, $plugSize, $controlKeyString, $inputXml, $disconnect = false)
{
$toolkitLib = $this->getOption('XMLServiceLib');
$schemaSep = $this->getOption('schemaSep');
$transportType = $this->getOption('transportType');
$plugPrefix = $this->getOption('plugPrefix');
// construct plug name from prefix + size
$plug = $plugPrefix . $plugSize; // e.g. iPLUG512K
if ($plugPrefix == 'iPLUG') {
// db2 driver stored procedures take 4 params
$sql = "call {$toolkitLib}{$schemaSep}{$plug}(?,?,?,?)";
} else { /*odbc, iPLUGR */
// only three params for odbc stored procedures
$sql = "call {$toolkitLib}{$schemaSep}{$plug}(?,?,?)";
}
$bindArray = array(
'internalKey' => $internalKey,
'controlKey' => $controlKeyString,
'inputXml' => $inputXml,
'outputXml' => '',
'disconnect' => $disconnect
);
// if debug mode, log control key, stored procedure statement, and input XML.
if ($this->isDebug()) {
$this->debugLog("\nExec start: " . date("Y-m-d H:i:s") . "\nVersion of toolkit front end: " . self::getFrontEndVersion() ."\nIPC: '" . $this->getInternalKey() . "'. Control key: $controlKeyString\nStmt: $sql with transport: $transportType\nInput XML: $inputXml\n");
$this->execStartTime = microtime(true);
}
// can return false if prepare or exec failed.
$outputXml = $this->db->execXMLStoredProcedure($this->conn, $sql, $bindArray);
if (false === $outputXml) {
// if false returned, was a database error (stored proc prepare or execute error)
// @todo add ODBC SQL State codes
// If can't find stored proc for ODBC: Database code (if any): S1000. Message: [unixODBC][IBM][System i Access ODBC Driver][DB2 for i5/OS]SQL0440 - Routine IPLUG512K in XMLSERVICE not found with specified parameters.
//Warning: odbc_prepare(): SQL error: [unixODBC][IBM][System i Access ODBC Driver][DB2 for i5/OS]SQL0440 - Routine IPLUG512K in XMLSERVICE not found with specified parameters., SQL state S1000 in SQLPrepare in /usr/local/zend/ToolkitAPI/Odbcsupp.php on line 89
$this->cpfErr = $this->db->getErrorCode();
$this->setErrorMsg($this->db->getErrorMsg());
$errorReason = $this->getErrorReason($plugSize);
logThis($errorReason);
die($errorReason);
}
if ($disconnect) {
$this->db->disconnect($this->conn);
if ($this->isDebug()) {
$this->debugLog("Db disconnect requested and done.\n");
} //(debug)
}
return $outputXml;
}
protected function getErrorReason($plugSize)
{
$serviceLibrary = $this->getOption('XMLServiceLib');
if ($this->cpfErr == 22001) {
//22001 = On db2_execute, plug was too small to get input XML.
$errorReason = "Error: XML input was too large for the current plug size, '$plugSize'. Set a larger plugSize.";
} elseif ($this->cpfErr == 22003) {
//22003 = On db2_execute, plug was too small to get output XML.
$errorReason = "Error: XML output was too large for the current plug size, '$plugSize'. Set a larger plugSize.";
} elseif ($this->cpfErr == 22501) {
//22501 = Probably missing the LOB DB2 PTF. Get the latest DB2 Group PTF or CUME.
// or individual PTF:
// V5R4: 5722SS1 SI39610
// 6.1: 5761SS1 SI39829
// 7.1: 5770SS1 SI39831/SI39917
$errorReason = "Error: http://forums.zend.com/viewtopic.php?f=113&t=45413 Message: {$this->error}.";
} elseif ($this->cpfErr == 38501) {
// SQLState 38501: error in stored procedure. Possibly trace=true but library XMLSERVLOG doesn't exist.
// or Trigger program or external routine detected an error. SQLCODE=-443
$errorReason = "Error: SQLState 38501. Message: {$this->error}. Error in stored procedure or program. Could be a library that does not exist. If trace=true (not debug--trace), make sure XMLSERVLOG exists.";
} elseif ($this->cpfErr == 42704) {
//42704 = obj not found
$errorReason = "Error: Toolkit not found in specified service library ($serviceLibrary).";
} elseif ($this->cpfErr == 42833) {
//42833 = The qualified object name is inconsistent with the naming option.
$errorReason = "Error: i5_naming mismatch. When connecting to the toolkit, you specified i5_naming='$this->_i5NamingFlag' (or left the default of 0), which does not match the naming mode of the database job. Solution: If you are using a persistent database connection, ensure that every time you connect to db2 or the toolkit with a given user profile, you specify the same naming value for 'i5_naming'. If you are using library lists or a schema separator of '/', the naming mode must be '1'.";
} elseif ($this->cpfErr == 58004) {
//58004 = The qualified object name is inconsistent with the naming option.
$errorReason = "Error: Message: {$this->error}. SQLState 58004. If SQLCode is -901, check previous messages in joblog. Could mean an incorrect library in library list or another previous error in database job.";
} elseif ($this->cpfErr == '' || $this->cpfErr == 'HY017') {
// no SQLSTATE or HY017: The DB2 QSQSRVR job no longer exists.
$errorReason = "Error: SQLSTATE='{$this->cpfErr}' and Message: '{$this->error}', indicating that this PHP job's associated database job is no longer running.";
} else {
$errorReason = "Toolkit request failed. Review the database code and message.";
$errorReason .= " Database code (if any): '{$this->cpfErr}'. Message: {$this->error}";
}
// other codes: SQLState 38501: error in stored procedure. Possibly trace=true but library XMLSERVLOG doesn't exist.
// 38501: Trigger program or external routine detected an error. SQLCODE=-443
// could be bad library in libl.
// in joblog, see:
/* CPF2110 Library GLUMP not found.
* then:
* SQL0443 Message . . . . : Trigger program or external routine detected an error.
Cause . . . . . : Either a trigger program, external procedure, or external
function detected and returned an error to SQL. If the error occurred in a
trigger program, the trigger was on table QCMDEXC in schema QSYS. If the
error occurred in an external procedure or function, the external name is
QCMDEXC in schema QSYS. The associated text is Library GLUMP not found..
If the error occurred in a trigger program, the associated text is the type
of trigger program. If the error occurred in an external function, the
associated text is the text of the error message returned from the external
function.
*/
return $errorReason;
}
/**
* Alias of ExecuteProgram();
* Send any XML to XMLSERVICE toolkit. The XML doesn't have to represent a program.
*
* @todo create method to parse XML appropriately no matter what type of tags (cmd/pgm etc.)
*