// This module is included in the following assembly:
// as_developing-extensions.adoc

[id='develop-connector-extensions_{context}']
= Example of developing a connector extension

If {prodname} does not provide a connector for the application or 
service that you want to connect to in an integration, 
an experienced developer can code an extension that contributes a 
new connector to {prodname}. This documentation provides an introduction
to developing a connector extension. For details about developing a 
connector, see 
link:https://syndesis.io/docs/connectors/[Developing Syndesis connectors]
on the Syndesis community site. 

[IMPORTANT]
====
For connector extensions, it is not yet possible to automatically generate the 
extension definition JSON file from Java code.
====

A connector is essentially a proxy for a Camel component. A connector configures the 
underlying component and creates endpoints according to 
options that are defined in the extension definition and in 
user-supplied options that the {prodname} web interface collects.

The connector extension definition extends the extension definition 
that is required for step extensions with the following additional data
structures:

* `componentScheme`
+
Defines the Camel component that the connector uses. 
You can set `componentScheme` for the connector or for actions. 
If you set `componentScheme` for both the connector and an action,
the setting for the action has precedence. 

* `connectorCustomizers`
+
Specifies a list of classes that implement the 
link:https://github.com/syndesisio/syndesis/blob/master/app/integration/component-proxy/src/main/java/io/syndesis/integration/component/proxy/ComponentProxyCustomizer.java[ComponentProxyCustomizer] 
class. Each class customizes the behavior of a connector. For example,
a class might manipulate
properties before they are applied to the underlying component/endpoint, 
or a class might add pre/post endpoint logic. For each class, specify 
the full class name of the implementation, for example, `com.mycomponent.MyCustomizer`. 
You can set `connectorCustomizers` on actions as well as connectors. 
According to what is set, 
{prodname} applies customizers to the connector first and then to 
actions. 

* `connectorFactory`
+
Defines the class that implements the
link:https://github.com/syndesisio/syndesis/blob/master/app/integration/component-proxy/src/main/java/io/syndesis/integration/component/proxy/ComponentProxyFactory.java[ComponentProxyFactory] 
class, which creates and/or configures the underlying component/endpoint.  
Specify the full class name of the implementation. 
You can set `connectorFactory` for the connector or for actions. 
Actions have precedence. 

.Customizer example

The following customizer example sets up a `DataSource` from individual options:

[source,java]
----
public class DataSourceCustomizer implements ComponentProxyCustomizer, CamelContextAware {
    private final static Logger LOGGER = LoggerFactory.getLogger(DataSourceCustomizer.class);

    private CamelContext camelContext;

    @Override
    public void setCamelContext(CamelContext camelContext) { // <1>
        this.camelContext = camelContext;
    }

    @Override
    public CamelContext getCamelContext() { // <1>
        return this.camelContext;
    }

    @Override
    public void customize(ComponentProxyComponent component, Map<String, Object> options) {
        if (!options.containsKey("dataSource")) {
            if (options.containsKey("user") && options.containsKey("password") && options.containsKey("url")) {
                try {
                    BasicDataSource ds = new BasicDataSource();

                    consumeOption(camelContext, options, "user", String.class, ds::setUsername); // <2>
                    consumeOption(camelContext, options, "password", String.class, ds::setPassword); // <2>
                    consumeOption(camelContext, options, "url", String.class, ds::setUrl); // <2>

                    options.put("dataSource", ds);
                } catch (@SuppressWarnings("PMD.AvoidCatchingGenericException") Exception e) {
                    throw new IllegalArgumentException(e);
                }
            } else {
                LOGGER.debug("Not enough information provided to set-up the DataSource");
            }
        }
    }
}
----
<1> &nbsp;By implementing `CamelContextAware`, Syndesis injects the Camel context and then invokes the customize method. 
<2> &nbsp;Processes options and then removes them from the options map.

.Example of injecting properties

If the customizer respects Java bean conventions, you can also inject 
the properties, as shown in this revision of the previous example: 

[source,java]
----
public class DataSourceCustomizer implements ComponentProxyCustomizer, CamelContextAware {
    private final static Logger LOGGER = LoggerFactory.getLogger(DataSourceCustomizer.class);

    private CamelContext camelContext;
    private String userName;
    private String password;
    private String url;

    @Override
    public void setCamelContext(CamelContext camelContext) { // <1>
        this.camelContext = camelContext;
    }

    @Override
    public CamelContext getCamelContext() { // <1>
        return this.camelContext;
    }

    public void setUserName(String userName) { //<1>
      this.userName = userName;
    }

    public String getUserName() { // <2>
      return this.userName;
    }

    public void setPassword(String password) { // <2>
      this.password = password;
    }

    public String getPassword() { // <2>
      return this.password;
    }

    public void setUrl(String url) { // <2>
      this.url = url;
    }

    public String getUrl() { // <2>
      return this.url;
    }

    @Override
    public void customize(ComponentProxyComponent component, Map<String, Object> options) {
        if (!options.containsKey("dataSource")) {
            if (userName != null && password != null && url != null) {
                try {
                    BasicDataSource ds = new BasicDataSource();
                    ds.setUserName(userName);
                    ds.setPassword(password);
                    ds.setUrl(url);

                    options.put("dataSource", ds);
                } catch (@SuppressWarnings("PMD.AvoidCatchingGenericException") Exception e) {
                    throw new IllegalArgumentException(e);
                }
            } else {
                LOGGER.debug("Not enough information provided to set-up the DataSource");
            }
        }
    }
}
----
<1> &nbsp;By implementing `CamelContextAware`, Syndesis injects the Camel context 
and then invokes the customize method. This sample code overrides the 
`setCamelContext()` and `getCamelContext()` methods, and sets the user name. 
<2> &nbsp;The sample code processes the injected options and 
automatically removes them from the options map.

.Using a customizer to configure before/after logic

You can use a customizer to configure before/after logic as shown in this
example:

[source,java]
----
public class AWSS3DeleteObjectCustomizer implements ComponentProxyCustomizer {
  	private String filenameKey;

    public void setFilenameKey(String filenameKey) {
      this.filenameKey = filenameKey;
    }

    public String getFilenameKey() {
        return this.filenameKey;
    }

    @Override
    public void customize(ComponentProxyComponent component, Map<String, Object> options) {
      	component.setBeforeProducer(this::beforeProducer);
    }

    public void beforeProducer(final Exchange exchange) throws IOException {
      	exchange.getIn().setHeader(S3Constants.S3_OPERATION, S3Operations.deleteObject);

    	  if (filenameKey != null) {
    		    exchange.getIn().setHeader(S3Constants.KEY, filenameKey);
    	  }
    }
}
----

.Customizing behavior of `ComponentProxyComponent`

The 
link:https://github.com/syndesisio/syndesis/blob/master/app/integration/component-proxy/src/main/java/io/syndesis/integration/component/proxy/ComponentProxyFactory.java[ComponentProxyFactory] 
class creates and/or configures the underlying component/endpoint. 
To customize the behavior of the 
https://github.com/syndesisio/syndesis/blob/master/app/integration/component-proxy/src/main/java/io/syndesis/integration/component/proxy/ComponentProxyComponent.java[ComponentProxyComponent] 
object that `ComponentProxyFactory` creates, you can override any of the following methods:

* `createDelegateComponent()`
+
Syndesis invokes this method when the proxy starts and it is used to eventually 
create a dedicated instance of the component with the scheme defined by the 
`componentScheme` option.
+
The default behavior of this method is to determine if any of the 
connector/action options applies at the component level. Only in the 
case that the same option cannot be applied at the endpoint, 
the method creates a custom component instance and configures it 
according to the applicable options.

* `configureDelegateComponent()`
+
Syndesis invokes this method only if a custom component instance has been 
created to configure additional behavior of the delegated component instance.

* `createDelegateEndpoint()`
+
Syndesis invokes this method when the proxy creates the endpoint and by 
default creates the endpoint by using Camel catalog facilities.

* `configureDelegateEndpoint()`
+
After the delegated endpoint has been created, Syndesis 
invokes this method to configure additional behavior of the delegated endpoint 
instance, for example:
+
[source,java]
----
public class IrcComponentProxyFactory implements ComponentProxyFactory {

    @Override
    public ComponentProxyComponent newInstance(String componentId, String componentScheme) {
        return new ComponentProxyComponent(componentId, componentScheme) {
            @Override
            protected void configureDelegateEndpoint(ComponentDefinition definition, Endpoint endpoint, Map<String, Object> options) throws Exception {
                if (!(endpoint instanceof IrcEndpoint)) {
                    throw new IllegalStateException("Endpoint should be of type IrcEndpoint");
                }

                final IrcEndpoint ircEndpoint = (IrcEndpoint)endpoint;
                final String channels = (String)options.remove("channels");

                if (ObjectHelper.isNotEmpty(channels)) {
                    ircEndpoint.getConfiguration().setChannel(
                        Arrays.asList(channels.split(","))
                    );
                }
            }
        };
    }
}
----
