001package com.pingidentity.ds.plugin;
002
003import com.unboundid.directory.sdk.common.api.MonitorProvider;
004import com.unboundid.directory.sdk.common.operation.*;
005import com.unboundid.directory.sdk.common.types.ActiveOperationContext;
006import com.unboundid.directory.sdk.common.types.ActiveSearchOperationContext;
007import com.unboundid.directory.sdk.common.types.RegisteredMonitorProvider;
008import com.unboundid.directory.sdk.ds.api.Plugin;
009import com.unboundid.directory.sdk.ds.config.PluginConfig;
010import com.unboundid.directory.sdk.ds.types.DirectoryServerContext;
011import com.unboundid.directory.sdk.ds.types.PreParsePluginResult;
012import com.unboundid.ldap.sdk.Attribute;
013import com.unboundid.ldap.sdk.Control;
014import com.unboundid.ldap.sdk.LDAPException;
015import com.unboundid.ldap.sdk.ResultCode;
016import com.unboundid.ldap.sdk.controls.*;
017import com.unboundid.ldap.sdk.unboundidds.controls.*;
018import com.unboundid.util.StaticUtils;
019import com.unboundid.util.args.ArgumentException;
020import com.unboundid.util.args.ArgumentParser;
021import com.unboundid.util.args.StringArgument;
022
023import java.util.*;
024import java.util.concurrent.atomic.AtomicLong;
025
026/**
027 * This class provides
028 */
029public class ControlsInjector extends Plugin {
030    private static final String CONTROL_ADMINISTRATIVE_OPERATION = "AdministrativeOperation";
031    private static final String CONTROL_DONT_USE_COPY = "DontUseCopy";
032    private static final String CONTROL_EXTENDED_SCHEMA_INFO = "ExtendedSchemaInfo";
033    private static final String CONTROL_HARD_DELETE = "HardDelete";
034    private static final String CONTROL_IGNORE_NO_USER_MODIFICATION = "IgnoreNoUserModification";
035    private static final String CONTROL_MANAGE_DSA_IT = "ManageDsaIT";
036    private static final String CONTROL_NAME_WITH_ENTRYUUD = "NameWithEntryUUID";
037    private static final String CONTROL_NOOP = "NoOp";
038    private static final String CONTROL_PERMISSIVE_MODIFY = "PermissiveModify";
039    private static final String CONTROL_REAL_ATTRIBUTES_ONLY = "RealAttributesOnly";
040    private static final String CONTROL_REPLICATION_REPAIR = "ReplicationRepair";
041    private static final String CONTROL_RETAIN_IDENTITY = "RetainIdentity";
042    private static final String CONTROL_RETURN_CONFLICT_ENTRIES = "ReturnConflictEntries";
043    private static final String CONTROL_SUBENTRIES = "Subentries";
044    private final static String CONTROL_SUBTREE_DELETE = "SubtreeDelete";
045    private final static String CONTROL_SUPPRESS_REFERENTIAL_INTEGRITY_UPDATES = "SuppressReferentialIntegrityUpdates";
046    private final static String CONTROL_UNDELETE = "Undelete";
047    private final static String CONTROL_VIRTUAL_ATTRIBUTES_ONLY = "VirtualAttributesOnly";
048
049    private final static String OP_ABANDON = "abandon";
050    private final static String OP_ADD = "add";
051    private final static String OP_BIND_SIMPLE = "simple-bind";
052    private final static String OP_BIND_SASL = "sasl-bind";
053    private final static String OP_COMPARE = "compare";
054    private final static String OP_DELETE = "delete";
055    private final static String OP_EXTENDED = "extended";
056    private final static String OP_MODIFY = "modify";
057    private final static String OP_MODIFY_DN = "moddn";
058    private final static String OP_SEARCH = "search";
059    private final static String OP_UNBIND = "unbind";
060    private final static String[] OPERATIONS = new String[]{
061            OP_ABANDON,
062            OP_ADD,
063            OP_BIND_SIMPLE,
064            OP_BIND_SASL,
065            OP_COMPARE,
066            OP_EXTENDED,
067            OP_MODIFY,
068            OP_MODIFY_DN,
069            OP_SEARCH,
070            OP_UNBIND
071    };
072
073
074    private static final String[] SUPPORTED_REQUEST_CONTROLS = new String[]{
075            CONTROL_ADMINISTRATIVE_OPERATION,
076            CONTROL_DONT_USE_COPY,
077            CONTROL_EXTENDED_SCHEMA_INFO,
078            CONTROL_HARD_DELETE,
079            CONTROL_IGNORE_NO_USER_MODIFICATION,
080            CONTROL_MANAGE_DSA_IT,
081            CONTROL_NAME_WITH_ENTRYUUD,
082            CONTROL_NOOP,
083            CONTROL_PERMISSIVE_MODIFY,
084            CONTROL_REAL_ATTRIBUTES_ONLY,
085            CONTROL_REPLICATION_REPAIR,
086            CONTROL_RETAIN_IDENTITY,
087            CONTROL_RETURN_CONFLICT_ENTRIES,
088            CONTROL_SUBENTRIES,
089            CONTROL_SUBTREE_DELETE,
090            CONTROL_SUPPRESS_REFERENTIAL_INTEGRITY_UPDATES,
091            CONTROL_UNDELETE,
092            CONTROL_VIRTUAL_ATTRIBUTES_ONLY
093    };
094    private static final HashSet<String> SUPPORTED_REQUEST_CONTROLS_SET = new HashSet<>(Arrays.asList
095            (SUPPORTED_REQUEST_CONTROLS));
096
097    private static final String ARG_REQUEST_CONTROL_NAME = "request-control";
098    private Control additionalRequestControl;
099    private HashMap<String,AtomicLong> counters = new HashMap<>(11);
100    private PluginConfig config;
101    private DirectoryServerContext serverContext;
102
103    private RegisteredMonitorProvider registeredMonitorProvider;
104
105    private String join(String separator, String[] array) {
106        if (array == null) {
107            return null;
108        }
109        if (separator == null) {
110            separator = "";
111        }
112        StringBuilder result = new StringBuilder();
113        for (String element : array) {
114            result.append(result.length() > 0 ? separator + element : element);
115        }
116        return result.toString();
117    }
118
119    @Override
120    public String getExtensionName() {
121        return "Controls Injector";
122    }
123
124    @Override
125    public String[] getExtensionDescription() {
126        return new String[]{"An extension that allows to inject controls to incoming requests.",
127                "Supported controls and their OIDs:" +
128                        "<ul>\n" +
129                        "<li>AdministrativeOperation (1.3.6.1.4.1.30221.2.5.11)</li>\n" +
130                        "<li>DontUseCopy (1.3.6.1.1.22)</li>\n" +
131                        "<li>ExtendedSchemaInfo (1.3.6.1.4.1.30221.2.5.12)</li>\n" +
132                        "<li>HardDelete (1.3.6.1.4.1.30221.2.5.22)</li>\n" +
133                        "<li>IgnoreNoUserModification (1.3.6.1.4.1.30221.2.5.5)</li>\n" +
134                        "<li>ManageDsaIT (2.16.840.1.113730.3.4.2)</li>\n" +
135                        "<li>NameWithEntryUUID (1.3.6.1.4.1.30221.2.5.44)</li>\n" +
136                        "<li>NoOp (1.3.6.1.4.1.4203.1.10.2)</li>\n" +
137                        "<li>PermissiveModify (1.2.840.113556.1.4.1413)</li>\n" +
138                        "<li>RealAttributesOnly (2.16.840.1.113730.3.4.17)</li>\n" +
139                        "<li>ReplicationRepair (1.3.6.1.4.1.30221.1.5.2)</li>\n" +
140                        "<li>RetainIdentity (1.3.6.1.4.1.30221.2.5.3)</li>\n" +
141                        "<li>ReturnConflictEntries (1.3.6.1.4.1.30221.2.5.13)</li>\n" +
142                        "<li>Subentries (1.3.6.1.4.1.7628.5.101.1)</li>\n" +
143                        "<li>SubtreeDelete (1.2.840.113556.1.4.805)</li>\n" +
144                        "<li>SuppressReferentialIntegrityUpdates (1.3.6.1.4.1.30221.2.5.30)</li>\n" +
145                        "<li>Undelete (1.3.6.1.4.1.30221.2.5.23)</li>\n" +
146                        "<li>VirtualAttributesOnly (2.16.840.1.113730.3.4.19)</li>\n" +
147                        "</ul>\n",
148                "The list above is provided because you may have to add ACI rules to allow for the control to be used. For example:" +
149                        "<pre>\n" +
150                        "(targetcontrol=\"1.3.6.1.4.1.4203.1.10.2\")(version 3.0; acl \"allow use of NoOp control on own entry\"; allow(read) userdn=\"ldap:///self\";)" +
151                        "</pre>\n",
152                "To create a plugin:\n" +
153                        "<pre>\n" +
154                        "dsconfig create-plugin \n" +
155                        "    --plugin-name noop-injector \n" +
156                        "    --type third-party \n" +
157                        "    --set enabled:false \n" +
158                        "    --set plugin-type:preparseabandon \n" +
159                        "    --set plugin-type:preparseadd \n" +
160                        "    --set plugin-type:preparsebind \n" +
161                        "    --set plugin-type:preparsecompare \n" +
162                        "    --set plugin-type:preparsedelete \n" +
163                        "    --set plugin-type:preparseextended \n" +
164                        "    --set plugin-type:preparsemodify \n" +
165                        "    --set plugin-type:preparsemodifydn \n" +
166                        "    --set plugin-type:preparsesearch \n" +
167                        "    --set plugin-type:preparseunbind \n" +
168                        "    --set extension-class:com.pingidentity.ds.plugin.ControlsInjector \n" +
169                        "    --set extension-argument:request-control=NoOp \n" +
170                        "    --set request-criteria:the-request-criteria-name" +
171                        "</pre>",
172                "To retrieve monitoring information, search cn=monitor like:\n" +
173                        "<pre>\n" +
174                        "ldapsearch -b cn=monitor '(objectClass=ds-monitor-controls-injector)'\n" +
175                        "dn: cn=noop-injector-monitor [from ThirdPartyPlugin:noop-injector],cn=monitor\n" +
176                        "objectClass: top\n" +
177                        "objectClass: ds-monitor-entry\n" +
178                        "objectClass: ds-monitor-controls-injector\n" +
179                        "objectClass: extensibleObject\n" +
180                        "cn: noop-injector-monitor [from ThirdPartyPlugin:noop-injector]\n" +
181                        "ds-extension-monitor-name: noop-injector-monitor\n" +
182                        "ds-extension-type: ThirdPartyPlugin\n" +
183                        "ds-extension-name: noop-injector\n" +
184                        "processed-add: 0\n" +
185                        "processed-modify: 0\n" +
186                        "processed-compare: 0\n" +
187                        "processed-search: 0\n" +
188                        "processed-sasl-bind: 0\n" +
189                        "processed-abandon: 0\n" +
190                        "processed-unbind: 0\n" +
191                        "processed-simple-bind: 0\n" +
192                        "processed-extended: 0\n" +
193                        "processed-moddn: 0" +
194                        "</pre>",
195                "This example plugin would inject the NoOp control, as illustrated below:\n" +
196                        "<pre>\n" +
197                        "ldapmodify\n" +
198                        "dn: uid=user.7,ou=People,dc=example,dc=com\n" +
199                        "changetype: modify\n" +
200                        "replace: description\n" +
201                        "description: controls injector test 4\n" +
202                        "\n" +
203                        "# Modifying entry uid=user.7,ou=People,dc=example,dc=com ...\n" +
204                        "# Result Code:  16654 (no operation)\n" +
205                        "# Diagnostic Message:  The modify operation was not actually performed in the Directory Server backend because the LDAP no-op control was present in the request" +
206                        "</pre>",
207        "NOTE: you probably should never use this plugin without a request criteria."
208        };
209    }
210
211    @Override
212    public void defineConfigArguments(ArgumentParser parser) throws ArgumentException {
213        parser.addArgument(new StringArgument(null, ARG_REQUEST_CONTROL_NAME, true, 1, "{control}",
214                "The name of a supported request control. Currently supported controls list: <br/>\n"
215                + "<ul><li>"
216                + join("</li>\n<li>", SUPPORTED_REQUEST_CONTROLS), SUPPORTED_REQUEST_CONTROLS_SET
217                + "</li></ul>"));
218    }
219
220    @Override
221    public void initializePlugin(DirectoryServerContext serverContext, PluginConfig config, ArgumentParser parser)
222            throws LDAPException {
223        this.config = config;
224
225        // Use the applyConfiguration method to set up the plugin.
226        final ArrayList<String> adminActionsRequired = new ArrayList<>(5);
227        final ArrayList<String> messages = new ArrayList<>(5);
228        final ResultCode resultCode = applyConfiguration(config, parser, adminActionsRequired, messages);
229        if (resultCode != ResultCode.SUCCESS) {
230            throw new LDAPException(resultCode,
231                    "One or more errors occurred while trying to initialize  " + getExtensionName() + " extension  in '"
232                            + config.getConfigObjectDN() + ":  " +
233                            StaticUtils.concatenateStrings(messages));
234        }
235        for (String operation: OPERATIONS )
236            counters.put(operation,new AtomicLong(0));
237        registeredMonitorProvider = serverContext.registerMonitorProvider(new ControlsInjectorMonitorProvider(), config);
238        this.serverContext = serverContext;
239    }
240
241    @Override
242    public void finalizePlugin() {
243        if ( registeredMonitorProvider != null)
244            serverContext.deregisterMonitorProvider(registeredMonitorProvider);
245    }
246
247    @Override
248    public ResultCode applyConfiguration(PluginConfig config, ArgumentParser parser, List<String>
249            adminActionsRequired, List<String> messages) {
250        String selectedRequestControl = parser.getStringArgument(ARG_REQUEST_CONTROL_NAME).getValue();
251        additionalRequestControl = null;
252        if (CONTROL_ADMINISTRATIVE_OPERATION.equalsIgnoreCase(selectedRequestControl)) {
253            additionalRequestControl = new AdministrativeOperationRequestControl();
254        }
255        if (CONTROL_DONT_USE_COPY.equalsIgnoreCase(selectedRequestControl)) {
256            additionalRequestControl = new DontUseCopyRequestControl();
257        }
258        if (CONTROL_EXTENDED_SCHEMA_INFO.equalsIgnoreCase(selectedRequestControl)) {
259            additionalRequestControl = new ExtendedSchemaInfoRequestControl();
260        }
261        if (CONTROL_HARD_DELETE.equalsIgnoreCase(selectedRequestControl)) {
262            additionalRequestControl = new HardDeleteRequestControl();
263        }
264        if (CONTROL_IGNORE_NO_USER_MODIFICATION.equalsIgnoreCase(selectedRequestControl)) {
265            additionalRequestControl = new IgnoreNoUserModificationRequestControl();
266        }
267        if (CONTROL_MANAGE_DSA_IT.equalsIgnoreCase(selectedRequestControl)) {
268            additionalRequestControl = new ManageDsaITRequestControl();
269        }
270        if (CONTROL_NAME_WITH_ENTRYUUD.equalsIgnoreCase(selectedRequestControl)) {
271            additionalRequestControl = new NameWithEntryUUIDRequestControl();
272        }
273        if (CONTROL_NOOP.equalsIgnoreCase(selectedRequestControl)) {
274            additionalRequestControl = new NoOpRequestControl();
275        }
276        if (CONTROL_PERMISSIVE_MODIFY.equalsIgnoreCase(selectedRequestControl)) {
277            additionalRequestControl = new PermissiveModifyRequestControl();
278        }
279        if (CONTROL_REAL_ATTRIBUTES_ONLY.equalsIgnoreCase(selectedRequestControl)) {
280            additionalRequestControl = new RealAttributesOnlyRequestControl();
281        }
282        if (CONTROL_REPLICATION_REPAIR.equalsIgnoreCase(selectedRequestControl)) {
283            additionalRequestControl = new ReplicationRepairRequestControl();
284        }
285        if (CONTROL_RETAIN_IDENTITY.equalsIgnoreCase(selectedRequestControl)) {
286            additionalRequestControl = new RetainIdentityRequestControl();
287        }
288        if (CONTROL_RETURN_CONFLICT_ENTRIES.equalsIgnoreCase(selectedRequestControl)) {
289            additionalRequestControl = new ReturnConflictEntriesRequestControl();
290        }
291        if (CONTROL_SUBENTRIES.equalsIgnoreCase(selectedRequestControl)) {
292            additionalRequestControl = new SubentriesRequestControl();
293        }
294        if (CONTROL_SUBTREE_DELETE.equalsIgnoreCase(selectedRequestControl)) {
295            additionalRequestControl = new SubtreeDeleteRequestControl();
296        }
297        if (CONTROL_SUPPRESS_REFERENTIAL_INTEGRITY_UPDATES.equalsIgnoreCase(selectedRequestControl)) {
298            additionalRequestControl = new SuppressReferentialIntegrityUpdatesRequestControl();
299        }
300        if (CONTROL_UNDELETE.equalsIgnoreCase(selectedRequestControl)) {
301            additionalRequestControl = new UndeleteRequestControl();
302        }
303        if ( CONTROL_VIRTUAL_ATTRIBUTES_ONLY.equalsIgnoreCase(selectedRequestControl)) {
304            additionalRequestControl = new VirtualAttributesOnlyRequestControl();
305        }
306        return ResultCode.SUCCESS;
307    }
308
309    /**
310     * Performs the necessary processing to add the configured control to ABANDON requests
311     *
312     * @param operationContext the operation context
313     * @param request          the request to update
314     * @return PreParsePluginResult.SUCCESS
315     */
316    @Override
317    public PreParsePluginResult doPreParse(ActiveOperationContext operationContext, UpdatableAbandonRequest request) {
318        counters.get(OP_ABANDON).incrementAndGet();
319        processUpdatableRequest(request);
320        return PreParsePluginResult.SUCCESS;
321    }
322
323    /**
324     * Performs the necessary processing to add the configured control to ADD requests
325     *
326     * @param operationContext the operation context
327     * @param request          the request to update
328     * @param result           the updatable result
329     * @return PreParsePluginResult.SUCCESS
330     */
331    @Override
332    public PreParsePluginResult doPreParse(ActiveOperationContext operationContext, UpdatableAddRequest request,
333                                           UpdatableAddResult result) {
334        counters.get(OP_ADD).incrementAndGet();
335        processUpdatableRequest(request);
336        return PreParsePluginResult.SUCCESS;
337    }
338
339    /**
340     * Performs the necessary processing to add the configured control to Simple BIND requests
341     *
342     * @param operationContext the operation context
343     * @param request          the request to update
344     * @param result           the updatable result
345     * @return PreParsePluginResult.SUCCESS
346     */
347    @Override
348    public PreParsePluginResult doPreParse(ActiveOperationContext operationContext, UpdatableSimpleBindRequest
349            request, UpdatableBindResult result) {
350        counters.get(OP_BIND_SIMPLE).incrementAndGet();
351        processUpdatableRequest(request);
352        return PreParsePluginResult.SUCCESS;
353    }
354
355    /**
356     * Performs the necessary processing to add the configured control to SASL BIND requests
357     *
358     * @param operationContext the operation context
359     * @param request          the request to update
360     * @param result           the updatable result
361     * @return PreParsePluginResult.SUCCESS
362     */
363    @Override
364    public PreParsePluginResult doPreParse(ActiveOperationContext operationContext, UpdatableSASLBindRequest request,
365                                           UpdatableBindResult result) {
366        counters.get(OP_BIND_SASL).incrementAndGet();
367        processUpdatableRequest(request);
368        return PreParsePluginResult.SUCCESS;
369    }
370
371    /**
372     * Performs the necessary processing to add the configured control to COMPARE requests
373     *
374     * @param operationContext the operation context
375     * @param request          the request to update
376     * @param result           the updatable result
377     * @return PreParsePluginResult.SUCCESS
378     */
379    @Override
380    public PreParsePluginResult doPreParse(ActiveOperationContext operationContext, UpdatableCompareRequest request,
381                                           UpdatableCompareResult result) {
382        counters.get(OP_COMPARE).incrementAndGet();
383        processUpdatableRequest(request);
384        return PreParsePluginResult.SUCCESS;
385    }
386
387    /**
388     * Performs the necessary processing to add the configured control to DELETE requests
389     *
390     * @param operationContext the operation context
391     * @param request          the request to update
392     * @param result           the updatable result
393     * @return PreParsePluginResult.SUCCESS
394     */
395    @Override
396    public PreParsePluginResult doPreParse(ActiveOperationContext operationContext, UpdatableDeleteRequest request,
397                                           UpdatableDeleteResult result) {
398        counters.get(OP_DELETE).incrementAndGet();
399        processUpdatableRequest(request);
400        return PreParsePluginResult.SUCCESS;
401    }
402
403    /**
404     * Performs the necessary processing to add the configured control to EXTENDED requests
405     *
406     * @param operationContext the operation context
407     * @param request          the request to update
408     * @param result           the updatable result
409     * @return PreParsePluginResult.SUCCESS
410     */
411    @Override
412    public PreParsePluginResult doPreParse(ActiveOperationContext operationContext, UpdatableExtendedRequest request,
413                                           UpdatableExtendedResult result) {
414        counters.get(OP_EXTENDED).incrementAndGet();
415        processUpdatableRequest(request);
416        return PreParsePluginResult.SUCCESS;
417    }
418
419    /**
420     * Performs the necessary processing to add the configured control to MODIFY requests
421     *
422     * @param operationContext the operation context
423     * @param request          the request to update
424     * @param result           the updatable result
425     * @return PreParsePluginResult.SUCCESS
426     */
427    @Override
428    public PreParsePluginResult doPreParse(ActiveOperationContext operationContext, UpdatableModifyRequest request,
429                                           UpdatableModifyResult result) {
430        counters.get(OP_MODIFY).incrementAndGet();
431        processUpdatableRequest(request);
432        return PreParsePluginResult.SUCCESS;
433    }
434
435    /**
436     * Performs the necessary processing to add the configured control to MODIFY DN requests
437     *
438     * @param operationContext the operation context
439     * @param request          the request to update
440     * @param result           the updatable result
441     * @return PreParsePluginResult.SUCCESS
442     */
443    @Override
444    public PreParsePluginResult doPreParse(ActiveOperationContext operationContext, UpdatableModifyDNRequest request,
445                                           UpdatableModifyDNResult result) {
446        counters.get(OP_MODIFY_DN).incrementAndGet();
447        processUpdatableRequest(request);
448        return PreParsePluginResult.SUCCESS;
449    }
450
451    /**
452     * Performs the necessary processing to add the configured control to SEARCH requests
453     *
454     * @param operationContext the operation context
455     * @param request          the request to update
456     * @param result           the updatable result
457     * @return PreParsePluginResult.SUCCESS
458     */
459    @Override
460    public PreParsePluginResult doPreParse(ActiveSearchOperationContext operationContext, UpdatableSearchRequest
461            request, UpdatableSearchResult result) {
462        counters.get(OP_SEARCH).incrementAndGet();
463        processUpdatableRequest(request);
464        return PreParsePluginResult.SUCCESS;
465    }
466
467    /**
468     * Performs the necessary processing to add the configured control to UNBIND requests
469     *
470     * @param operationContext the operation context
471     * @param request          the request to update
472     * @return PreParsePluginResult.SUCCESS
473     */
474    @Override
475    public PreParsePluginResult doPreParse(ActiveOperationContext operationContext, UpdatableUnbindRequest request) {
476        counters.get(OP_UNBIND).incrementAndGet();
477        processUpdatableRequest(request);
478        return PreParsePluginResult.SUCCESS;
479    }
480
481    /**
482     * Convenience method to stay DRY in each operation-specific method
483     *
484     * @param request the updatable request to add controls to
485     */
486    private void processUpdatableRequest(UpdatableRequest request) {
487        if (additionalRequestControl != null) {
488            request.setRequestControls(addRequestControl(request.getRequestControls(), additionalRequestControl));
489        }
490    }
491
492    /**
493     * Convenience method to add a control to a list of controls
494     * If the additional control has the same OID as another control already in the list
495     * the additional control will override the control already in the list
496     *
497     * @param requestControls   the list of controls to add a control to
498     * @param additionalControl the additional control to add to the list
499     * @return the list of controls with the additional control
500     */
501    List<Control> addRequestControl(List<Control> requestControls, Control additionalControl) {
502        List<Control> result = new ArrayList<>();
503        if (requestControls != null) {
504            for (Control control : requestControls) {
505                // this test effectively results in additionalControl overriding any control in the list with that OID
506                if (!control.getOID().equals(additionalControl.getOID()))
507                    result.add(control);
508            }
509        }
510        result.add(additionalControl);
511        return result;
512    }
513
514    public class ControlsInjectorMonitorProvider extends MonitorProvider {
515        public ControlsInjectorMonitorProvider() {
516        }
517
518        @Override
519        public String getMonitorObjectClass() {
520            return "ds-monitor-controls-injector";
521        }
522
523        @Override
524        public String getExtensionName() {
525            return "ControlsInjectorMonitorProvider";
526        }
527
528        @Override
529        public String[] getExtensionDescription() {
530            return null;
531        }
532
533        @Override
534        public String getMonitorInstanceName() {
535            return config.getConfigObjectName()+"-monitor";
536        }
537
538        @Override
539        public List<Attribute> getMonitorAttributes() {
540            List<Attribute> result = new ArrayList<>(11);
541            for (String key: counters.keySet() )
542                result.add(new Attribute("processed-"+key,String.valueOf(counters.get(key).get())));
543            return result;
544        }
545    }
546}