f9a285fc5044e68c39fbc90950d202b8eca83978
[platform/core/api/webapi-plugins.git] / src / ml / js / ml_pipeline.js
1 /*
2  * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16
17 var kPipelineStateChangeListenerNamePrefix = 'MLPipelineStateChangeListener';
18 var kSinkListenerNamePrefix = 'MLPipelineSinkListener';
19 var kCustomFilterListenerNamePrefix = 'MLPipelineCustomFilterListener';
20
21 //PipelineManager::createPipeline() begin
22 var ValidPipelineDisposeExceptions = ['NotFoundError', 'NotSupportedError', 'AbortError'];
23
24 var nextPipelineId = 1;
25 function NextPipelineId() {
26     return nextPipelineId++;
27 }
28
29 var ValidPipelineManagerCreatePipelineExceptions = [
30     'InvalidValuesError',
31     'TypeMismatchError',
32     'NotSupportedError',
33     'SecurityError',
34     'AbortError'
35 ];
36
37 var CreatePipeline = function() {
38     privUtils_.log('Entered PipelineManager.createPipeline()');
39     var args = validator_.validateArgs(arguments, [
40         {
41             name: 'definition',
42             type: validator_.Types.STRING
43         },
44         {
45             name: 'listener',
46             type: validator_.Types.FUNCTION,
47             optional: true,
48             nullable: true
49         }
50     ]);
51
52     if (!args.has.definition) {
53         throw new WebAPIException(
54             WebAPIException.INVALID_VALUES_ERR,
55             'Invalid parameter: pipeline definition is mandatory'
56         );
57     }
58
59     var pipeline = new Pipeline(NextPipelineId());
60     var nativeArgs = {
61         id: pipeline._id,
62         definition: args.definition
63     };
64
65     if (args.listener) {
66         nativeArgs.listenerName = kPipelineStateChangeListenerNamePrefix + pipeline._id;
67         var stateChangeListener = function(stateObject) {
68             args.listener(stateObject.state);
69         };
70         native_.addListener(nativeArgs.listenerName, stateChangeListener);
71     }
72
73     var result = native_.callSync('MLPipelineManagerCreatePipeline', nativeArgs);
74
75     if (native_.isFailure(result)) {
76         if (nativeArgs.listenerName) {
77             native_.removeListener(nativeArgs.listenerName);
78         }
79         throw native_.getErrorObjectAndValidate(
80             result,
81             ValidPipelineManagerCreatePipelineExceptions,
82             AbortError
83         );
84     }
85
86     return pipeline;
87 };
88 //PipelineManager::createPipeline() end
89
90 //Pipeline::state begin
91 var ValidPipelineStateExceptions = ['NotSupportedError', 'AbortError'];
92 var Pipeline = function(id) {
93     Object.defineProperties(this, {
94         state: {
95             enumerable: true,
96             get: function() {
97                 var result = native_.callSync('MLPipelineGetState', {
98                     id: id
99                 });
100                 if (native_.isFailure(result)) {
101                     throw native_.getErrorObjectAndValidate(
102                         result,
103                         ValidPipelineStateExceptions,
104                         AbortError
105                     );
106                 }
107
108                 return result.result;
109             }
110         },
111         _id: {
112             value: id
113         }
114     });
115 };
116 //Pipeline::state end
117
118 //Pipeline::start() begin
119 var ValidPipelineStartStopExceptions = [
120     'NotFoundError',
121     'NotSupportedError',
122     'AbortError'
123 ];
124 Pipeline.prototype.start = function() {
125     var nativeArgs = {
126         id: this._id
127     };
128
129     var result = native_.callSync('MLPipelineStart', nativeArgs);
130     if (native_.isFailure(result)) {
131         throw native_.getErrorObjectAndValidate(
132             result,
133             ValidPipelineStartStopExceptions,
134             AbortError
135         );
136     }
137 };
138 //Pipeline::start() end
139
140 //Pipeline::stop() begin
141 Pipeline.prototype.stop = function() {
142     var nativeArgs = {
143         id: this._id
144     };
145
146     var result = native_.callSync('MLPipelineStop', nativeArgs);
147     if (native_.isFailure(result)) {
148         throw native_.getErrorObjectAndValidate(
149             result,
150             ValidPipelineStartStopExceptions,
151             AbortError
152         );
153     }
154 };
155 //Pipeline::stop() end
156
157 //Pipeline::dispose() begin
158 Pipeline.prototype.dispose = function() {
159     var result = native_.callSync('MLPipelineDispose', { id: this._id });
160
161     if (native_.isFailure(result)) {
162         throw native_.getErrorObjectAndValidate(
163             result,
164             ValidPipelineDisposeExceptions,
165             AbortError
166         );
167     }
168 };
169 //Pipeline::dispose() end
170
171 //Pipeline::getNodeInfo() begin
172 var NodeInfo = function(name, pipeline_id) {
173     Object.defineProperties(this, {
174         name: { enumerable: true, writable: false, value: name },
175         _pipeline_id: { value: pipeline_id }
176     });
177 };
178
179 var ValidPipelineGetNodeInfoExceptions = [
180     'InvalidValuesError',
181     'NotFoundError',
182     'NotSupportedError',
183     'AbortError'
184 ];
185
186 Pipeline.prototype.getNodeInfo = function() {
187     var args = validator_.validateArgs(arguments, [
188         {
189             name: 'name',
190             type: validator_.Types.STRING
191         }
192     ]);
193
194     var nativeArgs = {
195         id: this._id,
196         name: args.name
197     };
198
199     var result = native_.callSync('MLPipelineGetNodeInfo', nativeArgs);
200     if (native_.isFailure(result)) {
201         throw native_.getErrorObjectAndValidate(
202             result,
203             ValidPipelineGetNodeInfoExceptions,
204             AbortError
205         );
206     }
207
208     return new NodeInfo(args.name, this._id);
209 };
210 //Pipeline::getNodeInfo() end
211
212 //Pipeline::getSource() begin
213 var ValidInputTensorsInfoExceptions = ['NotFoundError', 'AbortError'];
214 function Source(name, pipeline_id) {
215     Object.defineProperties(this, {
216         name: {
217             enumerable: true,
218             value: name
219         },
220         inputTensorsInfo: {
221             get: function() {
222                 var result = native_.callSync('MLPipelineGetInputTensorsInfo', {
223                     id: this._pipeline_id,
224                     name: this.name
225                 });
226                 if (native_.isFailure(result)) {
227                     throw native_.getErrorObjectAndValidate(
228                         result,
229                         ValidInputTensorsInfoExceptions,
230                         AbortError
231                     );
232                 }
233
234                 return new TensorsInfo(result.id);
235             }
236         },
237         _pipeline_id: {
238             value: pipeline_id
239         }
240     });
241 }
242
243 var ValidPipelineGetSourceExceptions = [
244     'InvalidStateError',
245     'InvalidValuesError',
246     'NotFoundError',
247     'NotSupportedError',
248     'AbortError'
249 ];
250
251 Pipeline.prototype.getSource = function() {
252     var args = validator_.validateArgs(arguments, [
253         {
254             name: 'name',
255             type: validator_.Types.STRING
256         }
257     ]);
258
259     if (!args.has.name) {
260         throw new WebAPIException(
261             WebAPIException.INVALID_VALUES_ERR,
262             'Invalid parameter: name is mandatory'
263         );
264     }
265
266     var nativeArgs = {
267         id: this._id,
268         name: args.name
269     };
270
271     var result = native_.callSync('MLPipelineGetSource', nativeArgs);
272     if (native_.isFailure(result)) {
273         throw native_.getErrorObjectAndValidate(
274             result,
275             ValidPipelineGetSourceExceptions,
276             AbortError
277         );
278     }
279
280     return new Source(args.name, this._id);
281 };
282 //Pipeline::getSource() end
283
284 //Pipeline::getSwitch() begin
285 function Switch(name, type, pipeline_id) {
286     Object.defineProperties(this, {
287         name: {
288             enumerable: true,
289             value: name
290         },
291         type: {
292             enumerable: true,
293             value: type
294         },
295         _pipeline_id: {
296             value: pipeline_id
297         }
298     });
299 }
300
301 var ValidPipelineGetSwitchExceptions = [
302     'InvalidStateError',
303     'InvalidValuesError',
304     'NotFoundError',
305     'NotSupportedError',
306     'AbortError'
307 ];
308 Pipeline.prototype.getSwitch = function() {
309     var args = validator_.validateArgs(arguments, [
310         {
311             name: 'name',
312             type: validator_.Types.STRING
313         }
314     ]);
315
316     var nativeArgs = {
317         name: args.name,
318         id: this._id
319     };
320     var result = native_.callSync('MLPipelineGetSwitch', nativeArgs);
321     if (native_.isFailure(result)) {
322         throw native_.getErrorObjectAndValidate(
323             result,
324             ValidPipelineGetSwitchExceptions,
325             AbortError
326         );
327     }
328
329     return new Switch(nativeArgs.name, result.type, this._id);
330 };
331 //Pipeline::getSwitch() end
332
333 //Pipeline::getValve() begin
334 var ValidValveIsOpenAndSetOpenExceptions = [
335     'NotFoundError',
336     'NotSupportedError',
337     'AbortError'
338 ];
339 function Valve(name, pipeline_id) {
340     Object.defineProperties(this, {
341         name: {
342             enumerable: true,
343             value: name
344         },
345         _pipeline_id: {
346             value: pipeline_id
347         },
348         isOpen: {
349             get: function() {
350                 var result = native_.callSync('MLPipelineValveIsOpen', {
351                     id: this._pipeline_id,
352                     name: this.name
353                 });
354
355                 if (native_.isFailure(result)) {
356                     throw native_.getErrorObjectAndValidate(
357                         result,
358                         ValidValveIsOpenAndSetOpenExceptions,
359                         AbortError
360                     );
361                 }
362
363                 return result.result;
364             },
365             set: function() {},
366             enumerable: true
367         }
368     });
369 }
370
371 var ValidPipelineGetValveExceptions = [
372     'InvalidValuesError',
373     'NotFoundError',
374     'NotSupportedError',
375     'AbortError'
376 ];
377 Pipeline.prototype.getValve = function() {
378     var args = validator_.validateArgs(arguments, [
379         {
380             name: 'name',
381             type: validator_.Types.STRING
382         }
383     ]);
384
385     var nativeArgs = {
386         name: args.name,
387         id: this._id
388     };
389
390     var result = native_.callSync('MLPipelineGetValve', nativeArgs);
391     if (native_.isFailure(result)) {
392         throw native_.getErrorObjectAndValidate(
393             result,
394             ValidPipelineGetValveExceptions,
395             AbortError
396         );
397     }
398
399     return new Valve(nativeArgs.name, this._id);
400 };
401 //Pipeline::getValve() end
402
403 //Pipeline::registerSinkListener() begin
404 var ValidRegisterSinkListenerExceptions = [
405     'InvalidValuesError',
406     'NotFoundError',
407     'NotSupportedError',
408     'TypeMismatchError',
409     'AbortError'
410 ];
411 Pipeline.prototype.registerSinkListener = function() {
412     var args = validator_.validateArgs(arguments, [
413         {
414             name: 'name',
415             type: validator_.Types.STRING
416         },
417         {
418             name: 'sinkListener',
419             type: types_.FUNCTION
420         }
421     ]);
422
423     var nativeArgs = {
424         id: this._id,
425         name: args.name,
426         listenerName: kSinkListenerNamePrefix + args.name
427     };
428
429     var sinkListener = function(msg) {
430         var sinkData = new TensorsData(msg.tensorsDataId, msg.tensorsInfoId);
431         args.sinkListener(args.name, sinkData);
432     };
433     native_.addListener(nativeArgs.listenerName, sinkListener);
434
435     var result = native_.callSync('MLPipelineRegisterSinkListener', nativeArgs);
436     if (native_.isFailure(result)) {
437         native_.removeListener(nativeArgs.listenerName);
438
439         throw native_.getErrorObjectAndValidate(
440             result,
441             ValidRegisterSinkListenerExceptions,
442             AbortError
443         );
444     }
445 };
446 //Pipeline::registerSinkListener() end
447
448 //Pipeline::unregisterSinkListener() begin
449 var ValidUnregisterSinkListenerExceptions = [
450     'InvalidValuesError',
451     'NotFoundError',
452     'NotSupportedError',
453     'AbortError'
454 ];
455 Pipeline.prototype.unregisterSinkListener = function() {
456     var args = validator_.validateArgs(arguments, [
457         {
458             name: 'name',
459             type: validator_.Types.STRING
460         }
461     ]);
462
463     if (!args.has.name) {
464         throw new WebAPIException(
465             WebAPIException.INVALID_VALUES_ERR,
466             'Invalid parameter: sink name is mandatory'
467         );
468     }
469
470     var nativeArgs = {
471         id: this._id,
472         name: args.name
473     };
474
475     var result = native_.callSync('MLPipelineUnregisterSinkListener', nativeArgs);
476     if (native_.isFailure(result)) {
477         throw native_.getErrorObjectAndValidate(
478             result,
479             ValidUnregisterSinkListenerExceptions,
480             AbortError
481         );
482     }
483
484     var listenerName = kSinkListenerNamePrefix + args.name;
485     native_.removeListener(listenerName);
486 };
487 //Pipeline::unregisterSinkListener() end
488
489 var PropertyType = {
490     BOOLEAN: 'BOOLEAN',
491     DOUBLE: 'DOUBLE',
492     ENUM: 'ENUM',
493     INT32: 'INT32',
494     INT64: 'INT64',
495     UINT32: 'UINT32',
496     UINT64: 'UINT64',
497     STRING: 'STRING'
498 };
499 //NodeInfo::getProperty() begin
500 var ValidNodeInfoGetPropertyExceptions = [
501     'InvalidValuesError',
502     'NotFoundError',
503     'NotSupportedError',
504     'TypeMismatchError',
505     'AbortError'
506 ];
507 NodeInfo.prototype.getProperty = function() {
508     var args = validator_.validateArgs(arguments, [
509         {
510             name: 'name',
511             type: validator_.Types.STRING
512         },
513         {
514             name: 'propertyType',
515             type: types_.ENUM,
516             values: Object.keys(PropertyType)
517         }
518     ]);
519
520     var nativeArgs = {
521         id: this._pipeline_id,
522         nodeName: this.name,
523         name: args.name,
524         type: args.propertyType
525     };
526
527     var result = native_.callSync('MLPipelineNodeInfoGetProperty', nativeArgs);
528     if (native_.isFailure(result)) {
529         throw native_.getErrorObjectAndValidate(
530             result,
531             ValidNodeInfoGetPropertyExceptions,
532             AbortError
533         );
534     }
535
536     return result.property;
537 };
538 //NodeInfo::getProperty() end
539
540 //NodeInfo::setProperty() begin
541 var ValidNodeInfoSetPropertyExceptions = [
542     'InvalidValuesError',
543     'NotFoundError',
544     'NotSupportedError',
545     'TypeMismatchError',
546     'AbortError'
547 ];
548 NodeInfo.prototype.setProperty = function() {
549     var args = validator_.validateArgs(arguments, [
550         {
551             name: 'name',
552             type: validator_.Types.STRING
553         },
554         {
555             name: 'propertyType',
556             type: types_.ENUM,
557             values: Object.keys(PropertyType)
558         },
559         {
560             name: 'property',
561             type: types_.SIMPLE_TYPE
562         }
563     ]);
564
565     var nativeArgs = {
566         id: this._pipeline_id,
567         nodeName: this.name,
568         name: args.name,
569         type: args.propertyType,
570         property: args.property
571     };
572
573     var result = native_.callSync('MLPipelineNodeInfoSetProperty', nativeArgs);
574     if (native_.isFailure(result)) {
575         throw native_.getErrorObjectAndValidate(
576             result,
577             ValidNodeInfoSetPropertyExceptions,
578             AbortError
579         );
580     }
581 };
582 //NodeInfo::setProperty() end
583
584 //Source::inputData() begin
585 var ValidSourceInputDataExceptions = [
586     'InvalidStateError',
587     'NotFoundError',
588     'NotSupportedError',
589     'AbortError'
590 ];
591 Source.prototype.inputData = function() {
592     var args = validator_.validateArgs(arguments, [
593         {
594             name: 'data',
595             type: types_.PLATFORM_OBJECT,
596             values: TensorsData
597         }
598     ]);
599
600     var nativeArgs = {
601         id: this._pipeline_id,
602         name: this.name,
603         tensorsDataId: args.data._id
604     };
605
606     var result = native_.callSync('MLPipelineSourceInputData', nativeArgs);
607
608     if (native_.isFailure(result)) {
609         throw native_.getErrorObjectAndValidate(
610             result,
611             ValidSourceInputDataExceptions,
612             AbortError
613         );
614     }
615
616     return result.result;
617 };
618 //Source::inputData() end
619
620 //Switch::getPadList() begin
621 var ValidSwitchGetPadListExceptions = [
622     'InvalidStateError',
623     'NotFoundError',
624     'NotSupportedError',
625     'AbortError'
626 ];
627 Switch.prototype.getPadList = function() {
628     var nativeArgs = {
629         name: this.name,
630         id: this._pipeline_id
631     };
632
633     var result = native_.callSync('MLPipelineSwitchGetPadList', nativeArgs);
634
635     if (native_.isFailure(result)) {
636         throw native_.getErrorObjectAndValidate(
637             result,
638             ValidSwitchGetPadListExceptions,
639             AbortError
640         );
641     }
642
643     return result.result;
644 };
645 //Switch::getPadList() end
646
647 //Switch::select() begin
648 var ValidSwitchSelectExceptions = [
649     'InvalidValuesError',
650     'NotFoundError',
651     'NotSupportedError',
652     'AbortError'
653 ];
654 Switch.prototype.select = function() {
655     var args = validator_.validateArgs(arguments, [
656         {
657             name: 'padName',
658             type: validator_.Types.STRING
659         }
660     ]);
661
662     if (!args.has.padName) {
663         throw new WebAPIException(
664             WebAPIException.INVALID_VALUES_ERR,
665             'Invalid parameter: pad name is mandatory'
666         );
667     }
668
669     var nativeArgs = {
670         id: this._pipeline_id,
671         name: this.name,
672         padName: args.padName
673     };
674
675     var result = native_.callSync('MLPipelineSwitchSelect', nativeArgs);
676     if (native_.isFailure(result)) {
677         throw native_.getErrorObjectAndValidate(
678             result,
679             ValidSwitchSelectExceptions,
680             AbortError
681         );
682     }
683 };
684 //Switch::select() end
685
686 //Valve::setOpen() begin
687 Valve.prototype.setOpen = function() {
688     var args = validator_.validateArgs(arguments, [
689         {
690             name: 'open',
691             type: validator_.Types.BOOLEAN
692         }
693     ]);
694
695     if (!args.has.open) {
696         throw new WebAPIException(
697             WebAPIException.INVALID_VALUES_ERR,
698             'Invalid parameter: open is mandatory'
699         );
700     }
701
702     var nativeArgs = {
703         id: this._pipeline_id,
704         name: this.name,
705         open: args.open
706     };
707
708     var result = native_.callSync('MLPipelineValveSetOpen', nativeArgs);
709     if (native_.isFailure(result)) {
710         throw native_.getErrorObjectAndValidate(
711             result,
712             ValidValveIsOpenAndSetOpenExceptions,
713             AbortError
714         );
715     }
716 };
717 //Valve::setOpen() end
718 var MachineLearningPipeline = function() {};
719
720 MachineLearningPipeline.prototype.createPipeline = CreatePipeline;
721
722 //Pipeline::registerCustomFilter() begin
723 var ValidRegisterCustomFilterExceptions = [
724     'InvalidValuesError',
725     'NotSupportedError',
726     'TypeMismatchError',
727     'AbortError'
728 ];
729
730 var ValidCustomFilterOutputErrors = ['InvalidValuesError', 'AbortError'];
731
732 MachineLearningPipeline.prototype.registerCustomFilter = function() {
733     var args = validator_.validateArgs(arguments, [
734         {
735             name: 'name',
736             type: validator_.Types.STRING
737         },
738         {
739             name: 'customFilter',
740             type: types_.FUNCTION
741         },
742         {
743             name: 'inputInfo',
744             type: types_.PLATFORM_OBJECT,
745             values: TensorsInfo
746         },
747         {
748             name: 'outputInfo',
749             type: types_.PLATFORM_OBJECT,
750             values: TensorsInfo
751         },
752         {
753             name: 'errorCallback',
754             type: types_.FUNCTION,
755             optional: true,
756             nullable: true
757         }
758     ]);
759
760     var nativeArgs = {
761         name: args.name,
762         listenerName: kCustomFilterListenerNamePrefix + args.name,
763         inputTensorsInfoId: args.inputInfo._id,
764         outputTensorsInfoId: args.outputInfo._id
765     };
766
767     /*
768      * CustomFilter processing has 4 stages (the description below assumes
769      * the typical scenario with no errors):
770      * 1. (C++; non-main thread) C++ callback is called by the native API with input data.
771      * The C++ callback wraps native ml_tensors_data_h handles in TensorsData
772      * objects and sends them together with associated TensorsInfo to JS.
773      * 2. (JS; main thread) customFilterWrapper is called with the input data from C++
774      * as one of its arguments. User-provided callback processes the data.
775      * The input/output TensorsData that arrive to JS as CustomFilter arguments
776      * are unique in that they:
777      * - cannot be disposed, i.e. calling {input, output}.dispose() is no-op
778      * - input is immutable, i.e. calling input.setTensorRawData() is no-op
779      * output.setTensorRawData() modify the native nnstreamer object directly.
780      * 3. (C++; main thread) Sleeping callback thread is notified. If anything
781      * goes wrong, C++ function returns an error synchronously to stage 4.
782      * 4. (JS; main thread) If C++ returned a success, the operation stops.
783      * Otherwise, the error callback provided by the user is called.
784      * 5. (C++; non-main thread) C++ callback is woken up and returns the status
785      * received from user to pipeline.
786      */
787     var customFilterWrapper = function(msg) {
788         /*
789          * Check if stage 1. was successful.
790          */
791         if (native_.isFailure(msg)) {
792             native_.callIfPossible(args.errorCallback, customFilterErrorInJs);
793             return;
794         }
795
796         var inputData = new TensorsData(
797             msg.inputTensorsDataId,
798             msg.inputTensorsInfoId,
799             false
800         );
801         var outputData = new TensorsData(
802             msg.outputTensorsDataId,
803             msg.outputTensorsInfoId,
804             false
805         );
806
807         /*
808          * customFilterErrorInJs records errors caused by the CustomFilter callback
809          * provided by the user.
810          */
811         var customFilterErrorInJs = null;
812         var jsResponse = {
813             status: -1,
814             name: nativeArgs.name,
815             requestId: msg.requestId
816         };
817
818         try {
819             jsResponse.status = converter_.toLong(
820                 args.customFilter(inputData, outputData)
821             );
822         } catch (exception) {
823             var exceptionString =
824                 typeof exception.toString === 'function'
825                     ? exception.toString()
826                     : JSON.stringify(exception);
827             customFilterErrorInJs = new WebAPIException(
828                 WebAPIException.ABORT_ERR,
829                 'CustomFilter has thrown exception: ' + exceptionString
830             );
831         }
832
833         if (!customFilterErrorInJs && jsResponse.status > 0 && jsResponse.status !== 1) {
834             customFilterErrorInJs = new WebAPIException(
835                 WebAPIException.INVALID_VALUES_ERR,
836                 'The only legal positive value of status returned from CustomFilter is 1'
837             );
838             jsResponse.status = -1;
839         }
840
841         /*
842          * Entering stage 3.
843          */
844         var result = native_.callSync('MLPipelineManagerCustomFilterOutput', jsResponse);
845
846         /*
847          * Stage 4.
848          */
849         if (customFilterErrorInJs) {
850             /*
851              * If we detect that user-provided CustomFilter callback caused
852              * any errors in JS, the C++ layer gets the message to stop the
853              * pipeline (status == -1) and does not reply to JS with errors.
854              * Thus, "result" is a success we call the user-provided error
855              * callback here.
856              */
857             native_.callIfPossible(args.errorCallback, customFilterErrorInJs);
858         } else if (native_.isFailure(result)) {
859             var error = native_.getErrorObjectAndValidate(
860                 result,
861                 ValidCustomFilterOutputErrors,
862                 AbortError
863             );
864
865             native_.callIfPossible(args.errorCallback, error);
866         }
867     };
868
869     if (native_.listeners_.hasOwnProperty(nativeArgs.listenerName)) {
870         throw new WebAPIException(
871             WebAPIException.INVALID_VALUES_ERR,
872             '"' + nativeArgs.name + '" custom filter is already registered'
873         );
874     }
875
876     native_.addListener(nativeArgs.listenerName, customFilterWrapper);
877
878     var result = native_.callSync('MLPipelineManagerRegisterCustomFilter', nativeArgs);
879     if (native_.isFailure(result)) {
880         native_.removeListener(nativeArgs.listenerName);
881
882         throw native_.getErrorObjectAndValidate(
883             result,
884             ValidRegisterCustomFilterExceptions,
885             AbortError
886         );
887     }
888 };
889 //Pipeline::registerCustomFilter() end
890
891 //Pipeline::unregisterCustomFilter() begin
892 var ValidUnregisterCustomFilterExceptions = [
893     'InvalidValuesError',
894     'NotSupportedError',
895     'AbortError'
896 ];
897 MachineLearningPipeline.prototype.unregisterCustomFilter = function() {
898     var args = validator_.validateArgs(arguments, [
899         {
900             name: 'name',
901             type: validator_.Types.STRING
902         }
903     ]);
904
905     if (!args.has.name) {
906         throw new WebAPIException(
907             WebAPIException.INVALID_VALUES_ERR,
908             'Invalid parameter: custom filter name is mandatory'
909         );
910     }
911
912     var result = native_.callSync('MLPipelineManagerUnregisterCustomFilter', {
913         name: args.name
914     });
915     if (native_.isFailure(result)) {
916         throw native_.getErrorObjectAndValidate(
917             result,
918             ValidUnregisterCustomFilterExceptions,
919             AbortError
920         );
921     }
922
923     var customFilterListenerName = kCustomFilterListenerNamePrefix + args.name;
924     native_.removeListener(customFilterListenerName);
925 };
926 //Pipeline::unregisterCustomFilter() end
927
928 // ML Pipeline API