How to Implement a Connector

e-Spirit AG

2020-02-20
Table of Contents

1. Introduction

This manual will show you how to implement an extended version of the generic filesystem connector available to all TranslationStudio modules.

The connector will allow you to add any number of projects to the connector with their own settings:

  • Job configuration to determine when to start a translation for all pages/datasets using the respective project.
  • A notification email address.
  • Target folder that will contain copies of the translatable xml files.
  • A command that might be executed after the translatable xml files have been created.

Each connector consists of 2 projects:

  1. FirstSpirit module to provide the configuration panel.
  2. TranslationStudio JAR library with the connector implementation.

This example connector implementation will assume Netbeans as your IDE.

You will find a Javadoc available at api/javadoc/connector

2. Setup the projects

First, create a FirstSpirit module maven project CONNECTOR - Example - Frontend using the project template provided in your TranslationStudio package at

api/project-templates/module

Second, create a Connector maven project CONNECTOR - Example - Backend using the project template provided in your TranslationStudio package at

api/project-templates/connector

The POM files will require you to add the following dependencies to your projects:

  1. FirstSpirit access JAR (from your FirstSpirit installation).
  2. TranslationStudio Connector API jar (from your TranslationStudio package at /api/.

3. Implementation - CONNECTOR - Example - Frontend

Open the newly created project CONNECTOR - Example - Frontend and create a new JPanel class

com.idmedia.translationstudio.connector.example.configuration.ConfigurationPanel extending javax.swing.JPanel

Now, design the panel similar to this

Sample Dialog
Figure 1. Sample Dialog


This tutorial will not provide code example to design the GUI but will only focus on the methods relevant to the connector.

3.1. Prerequisites

The JList on the left should have the following declaration

javax.swing.JList<IConfiguredProject> jListProjects

Create the following member variable

private final DefaultListModel<ConfiguredProject> m_pProjectListModel = new DefaultListModel();

and assign it as model to

jListProjects

3.2. Making the panel available

In order to make the panel available to TranslationStudio, add the following method:

/**
 * Get the configuration panel instance to be displayed by TranslationStudio
 * @return Panel
 */
@NotNull
JPanel getPanel()
{
    return this;
}

3.3. Saving the panel

To store potential changes made to the projects from the list, add the following methods:

/**
 * Create a configuration that will be passed on to the init method of this class
 * to initialize the configuration panel.
 * @return
 */
@NotNull
String onSaveGuiConfiguration()
{
    return onSaveConnectorConfiguration();
}

/**
 * Create a configuration that will be used by the connector implementation.
 * @return
 */
@NotNull
String onSaveConnectorConfiguration()
{
    final StringBuilder psXml = new StringBuilder(0);

    psXml.append("<configuration><projects>");

    final DefaultListModel pModel = m_pProjectListModel;
    final int nSize = pModel.getSize();

    for (int i = 0; i < nSize; i++)
        psXml.append(((ConfiguredProject)pModel.get(i)).toGuiXml());

    psXml.append("</projects>");
    psXml.append("</configuration>");

    return psXml.toString();
}

These methods will basically create an XML with all projects available to the list component. The very same XML will also be used to initialise the panel on startup.

3.4. Initialising the panel

Since this panel will now be accessible, we need to add a method to initialise it once it has been created.

Therefore, add the following method and ignore potential errors for the moment:

/**
 * Init the panel right after it is being displayed
 * @param sXml Gui Configuration as provided by onSaveGuiConfiguration()
 * @param vpJobs List of available job export configurations
 */
void onInit(String sXml, List<IProjectJobConfigurtion> vpJobs)
{
    if (vpJobs != null)
        m_vpJobs.addAll(vpJobs);

    /**
     * add all jobs
     */
    for (IProjectJobConfigurtion _job : m_vpJobs)
        jJobs.addItem(_job);

    /**
     * disable fields
     */
    enableEditableFields(false);

    /**
     * load from xml
     */
    final List<ConfiguredProject> vpElements = ConfiguredProject.fromXml(sXml);

    /**
     * add all projects
     */
    final DefaultListModel pModel = (DefaultListModel) jListProjects.getModel();
    for (ConfiguredProject _prj : vpElements)
        pModel.addElement(_prj);

    /**
     * select first project
     */
    selectProject(0);

    /**
     * enable fields
     */
    if (!m_pProjectListModel.isEmpty())
        enableEditableFields(true);
}

3.5. Creating a list of configured projects

Finally, we have to provide a list of configured projects ready to be used by TranslationStudio. Therefore, add the following method:

/**
 * Create a list of configured projects that will be used by TranslationStudio and eventually made available
 * to the language mappings dropdown component.
 * @return
 */
@NotNull
List<ConfiguredProject> getConfiguredProjects()
{
    final List<ConfiguredProject> vpList = new ArrayList<>();
    final DefaultListModel pModel = m_pProjectListModel;
    final int nSize = pModel.getSize();

    for (int i = 0; i < nSize; i++)
        vpList.add(((ConfiguredProject)pModel.get(i)).duplicate());

    return vpList;
}

3.6. Creating the ConfiguredProject model

The methods above will require a new class ConfiguredProject to be created.

A sample implementation may look like the following - extra work required!

class ConfiguredProject extends XmlStorable<ConfiguredProject>
{
    private static final Logger LOGGER = Logger.getLogger(ConfiguredProject.class.getName());

    private String displayName;
    private String id;
    private String jobConfigurationId;
    private String notificationAddress;
    private String copyToDirectory;
    private boolean keepOriginalFiles = true;
    private String commandline;

    protected ConfiguredProject()
    {
        this(true);
    }

    protected ConfiguredProject(boolean bCreateId)
    {
        if (bCreateId)
            id = UUID.randomUUID().toString();

        displayName = "";
        jobConfigurationId = "";
        copyToDirectory = "";
        commandline = "";
    }

    /**
     * Clone this project
     * @return
     */
    @NotNull
    @Override
    public ConfiguredProject duplicate()
    {
        final ConfiguredProject pThat = new ConfiguredProject(false);

        pThat.displayName = this.displayName;
        pThat.id = this.id;
        pThat.jobConfigurationId = this.jobConfigurationId;
        pThat.keepOriginalFiles = this.keepOriginalFiles;
        pThat.notificationAddress = this.notificationAddress;
        pThat.copyToDirectory = this.copyToDirectory;
        pThat.commandline = this.commandline;

        return pThat;
    }

    /**
     * Create an XML representation of this project
     * @return
     */
    @NotNull
    @Override
    public String toGuiXml()
    {
        final StringBuilder psXml = new StringBuilder();

        psXml.append("<roject>");
        psXml.append(createTag("id", id));
        psXml.append(createTag("displayName", displayName));
        psXml.append(createTag("jobConfigurationId", jobConfigurationId));
        psXml.append(createTag("email", notificationAddress));
        psXml.append(createTag("copyto", "" + copyToDirectory));
        psXml.append(createTag("keepfiles", keepOriginalFiles ? "true" : "false"));
        psXml.append(createTag("commandline", commandline));
        psXml.append("</roject>");

        return psXml.toString();
    }

    /**
     * Create an XML representation of this project
     * @return
     */
    @NotNull
    @Override
    public String toConnectorXml()
    {
        return toGuiXml();
    }

    /**
     * Create a list of projects from a stored xml
     * @param sXml
     * @return
     */
    @NotNull
    public static List<ConfiguredProject> fromXml(final String sXml)
    {
        final List<ConfiguredProject> vpList = new ArrayList<>();

        if (sXml == null || sXml.isEmpty())
            return vpList;

        int nStart = sXml.indexOf("<project>");
        while (nStart >= 0)
        {
            int nEnd = sXml.indexOf("</project>", nStart);
            if (nEnd < nStart)
                break;

            final ConfiguredProject pProject = fromSingleXml(sXml.substring(nStart, nEnd + 1));
            if (pProject != null)
                vpList.add(pProject);
            else
                LOGGER.log(Level.WARNING, "Could not create project from xml {0}", sXml.substring(nStart, nEnd + 1));

            nStart = sXml.indexOf("<project>", nEnd);
        }

        return vpList;
    }

    /**
     * Create a project from its stored xml
     *
     * @param sXml
     * @return
     */
    @Nullable
    private static ConfiguredProject fromSingleXml(final String sXml)
    {
        if (sXml == null || sXml.isEmpty())
            return null;

        final ConfiguredProject pPrj = new ConfiguredProject(false);

        pPrj.id = pPrj.extractSimpleTag("id", sXml);
        pPrj.displayName = pPrj.extractSimpleTag("displayName", sXml);
        pPrj.jobConfigurationId = pPrj.extractSimpleTag("jobConfigurationId", sXml);
        pPrj.notificationAddress = pPrj.extractSimpleTag("email", sXml);
        pPrj.keepOriginalFiles = "true".equals(pPrj.extractSimpleTag("keepfiles", sXml));
        pPrj.copyToDirectory = pPrj.extractSimpleTag("copyto", sXml);
        pPrj.commandline = pPrj.extractSimpleTag("commandline", sXml);

        return pPrj.id == null || pPrj.id.isEmpty() ? null : pPrj;
    }

    /**
     * Update Display name
     * @param displayName
     */
    public void setDisplayName(String displayName)
    {
        this.displayName = displayName;
    }

    /**
     * Update internal id
     * @param id
     */
    public void setId(String id)
    {
        this.id = id;
    }

    public void setNotificationAddress(String notificationAddress)
    {
        this.notificationAddress = notificationAddress == null ? "" : notificationAddress;
    }

    public String getNotificationAddress()
    {
        return notificationAddress == null ? "" : notificationAddress;
    }

    /**
     * Update Job Configuraiton Id
     * @param jobConfigurationId
     */
    public void setJobConfigurationId(String jobConfigurationId)
    {
        this.jobConfigurationId = jobConfigurationId;
    }

    @NotNull
    public String getDisplayName()
    {
        return displayName == null ? "" : displayName;
    }

    /**
     * Get the project id TranslationStudio uses
     * @return
     */
    @NotNull
    public String getId()
    {
        return id == null ? "" : id;
    }

    /**
     * Get Job Configuration Id
     * @return
     */
    @NotNull
    public String getJobConfigurationId()
    {
        return jobConfigurationId == null ? "" : jobConfigurationId;
    }

    @Override
    public String toString()
    {
        return getDisplayName();
    }

    public boolean keepOriginalFiles()
    {
        return keepOriginalFiles;
    }

    public void keepOriginalFiles(boolean bKeep)
    {
        keepOriginalFiles = bKeep;
    }

    /**
     * Create a hash
     * @return
     */
    @Override
    public int simpleHash()
    {
        int hash = 1;

        hash = hash * 17 + getId().hashCode();
        hash = hash * 22 + getJobConfigurationId().hashCode();
        hash = hash * 25 + getNotificationAddress().hashCode();
        hash = hash * 25 + getDisplayName().hashCode();
        hash = hash * 26 + (""+keepOriginalFiles()).hashCode();
        hash = hash * 27 + getCommandLine().hashCode();
        hash = hash * 27 + getCopyToDirectory().hashCode();

        return hash;
    }

    String getCommandLine()
    {
        return commandline;
    }

    void setCommandLine(String text)
    {
        commandline = text;
    }

    String getCopyToDirectory()
    {
        return copyToDirectory;
    }

    void setCopyToDirectory(String sDir)
    {
        copyToDirectory = sDir;
    }
}

3.7. Making the connector available to TranslationStudio

Finally, it is time to allow TranslationStudio to access the connector with all its nice features. Therefore, create a new class

ConnectorConfigurationPanel

implementing

ITranslationMemorySystemConnectorConfigurationGui

The interface requires us to implement quite a number of methods, so we will dive in right away.

First, we need some member variables:

/**
 * Store connector configuration
 */
private String m_sXmlConnector = "";
private String m_sXmlGui = "";

/**
 * Talk to the TranslationStudio Application
 */
private IExchangeCommunicator m_pCommunicator = null;

/**
 * Hold instance of panel
 */
private ConfigurationPanel m_pPanel = null;

/**
 * List of projects available to the language mapping
 */
private final List<IProjectJobConfigurtion> m_vpJobs = new ArrayList<>();

Second, some methods provide functionality not needed here, so we get rid of these right now:

/**
 * {@inheritDoc }
 */
@Override
public void onCreated()
{
}

/**
 * {@inheritDoc }
 */
@Override
public void onShow()
{
}

Third, let’s initialise the connector configuration properly by saving the data provided once the connector has been created by the TranslationStudio Configuration Panel itself. This will allow us to the panel as it should be.

/**
 * {@inheritDoc }
 */
@Override
public void onInit(String sId, String sConfiguration, List<IProjectJobConfigurtion> vsAvailableConfigurations, IExchangeCommunicator pCommunicator)
{
    m_sXmlGui = sConfiguration == null ? "" : sConfiguration;

    if (vsAvailableConfigurations != null)
        m_vpJobs.addAll(vsAvailableConfigurations);

    m_pCommunicator = pCommunicator;
}

Fourth, what is a connector without a proper name:

/**
 * {@inheritDoc }
 */
@Override
public String getName()
{
    return "Example Connector";
}

Fifth, we have to provide the panel instance itself and initialise it properly. Otherwise, nothing will be seen at all…​

/**
 * {@inheritDoc }
 */
@Override
public JPanel getConfigurationPanel()
{
    if (m_pPanel == null)
    {
        m_pPanel = new ConfigurationPanel(m_pCommunicator);
        m_pPanel.onInit(m_sXmlGui, m_vpJobs);
    }

    return m_pPanel;
}

Sixth, saving everything and making it available is quite easy since we can delegate that to the panel itself:

/**
 * {@inheritDoc }
 */
@Override
public void onSave()
{
    m_sXmlConnector = m_pPanel == null ? "" : m_pPanel.onSaveConnectorConfiguration();
    m_sXmlGui = m_pPanel == null ? "" : m_pPanel.onSaveGuiConfiguration();
}

/**
 * {@inheritDoc }
 */
@NotNull
@Override
public String getGuiConfiguration()
{
    return m_sXmlGui;
}

/**
 * {@inheritDoc }
 */
@NotNull
@Override
public String getConnectorConfiguration()
{
    return m_sXmlConnector;
}

Finally, we have to make configured projects available to TranslationStudio. These can be selected in the language mapping page template.

/**
 * {@inheritDoc }
 * @return
 */
@Override
public List<IConfiguredTranslationMemoryProject> getProjects()
{
    final List<IConfiguredTranslationMemoryProject> vpList = new ArrayList<>();
    final List<ConfiguredProject> vpProjects = m_pPanel.getConfiguredProjects();
    for (ConfiguredProject _prj : vpProjects)
    {
        final ConfiguredProject pProject = _prj.duplicate();
        final IConfiguredTranslationMemoryProject _project = new IConfiguredTranslationMemoryProject()
        {
            @Override
            public String getId()
            {
                return pProject.getId();
            }

            @Override
            public String getName()
            {
                return pProject.getDisplayName();
            }

            @Override
            public List<String> getNotificationEmails()
            {
                return new ArrayList<>();
            }

            @Override
            public String getJobId()
            {
                return pProject.getJobConfigurationId();
            }
        };

        vpList.add(_project);
    }
    return vpList;
}

3.8. Renaming the FSM Module

You can rename your FSM module by editing the file ./src/main/resources/module.xml

In addition, you can rename the FSM file itself by ./pom.xml file

That was easy, was it not? Now that we have mastered the configuration panel, we can implement the connector itself.

4. Implementation - CONNECTOR - Example - Backend

Open the newly created project CONNECTOR - Example - Backend.

This project will provide the business logic of the connector.

com.idmedia.translationstudio.connector.example.impl.Connector implementing ITranslationMemoryConnector

The connector has three tasks to perform:

/**
 * Operation to transfer files into the TMS
 * @return
 */
public ITranslationOperation getTranslationOperation();

/**
 * Operation to query registered files
 * @return
 */
public ITranslationStatusQueryOperation getTranslationStatusQueryOperation();

/**
 * Operation to manage queries from the connector's configuration panel (ServerManager)
 * @param sQueryData Input data
 * @return
 */
public IQueryOperation getQueryOperation(String sQueryData);

Each of the first two operations will be requested from TranslationStudio after the setup method has been called:

/**
 * Setup the connector
 *
 * @param sTmsId Tms Id
 * @param sConfigurationXml Connector Configuration (as provided by {@link com.idmedia.translationstudio.api.connector.gui.ITranslationMemorySystemConnectorConfigurationGui.getConnectorConfiguration}
 * @param sWorkingDirectory Connector Working Directory
 * @param pContext Connector Context
 * @return
 */
public boolean setup(String sTmsId, String sConfigurationXml, String sWorkingDirectory, IConnectorContext pContext);

There are 2 essential parameters:

  1. sConfigurationXml
    This parameter provides the exact String provided by the connector configuration implementation of ITranslationMemorySystemConnectorConfigurationGui.getConnectorConfiguration()
  2. pContext
    This parameter provides an access point to the TranslationStudio API

In addition, you may react to any changes to your connector configuration (i.e. whenever the configuration has been updated using TranslationStudio’s configuration panel):

/**
 * Method called when the settings are updated using the ServerManager
 * @param sConfigurationXml Connector Configuration (as provided by {@link com.idmedia.translationstudio.api.connector.gui.ITranslationMemorySystemConnectorConfigurationGui.getConnectorConfiguration}
 */
public void onUpdateConfiguration(String sConfigurationXml);

Your implementation may access the entire TranslationStudio API via TranslationStudio.get() at any time. So, let us begin.

4.1. Setting it all up

Your implementation of the setup method has to evaluate the xml provided by the connector configuration panel. Since this is simple XML evaluation, I leave this up to you. However, you have to store the result in a member variable, for example:

/**
 * Hold the configuration
 */
private final Configuration m_pConfiguration = new Configuration();

/**
 * {@inheritDoc }
 */
@Override
public boolean setup(String sTmsId, String sConfigurationXml, String sWorkingDirectory, IConnectorContext pContext)
{
    m_pConfiguration.load(sConfigurationXml);
    return true;
}

4.2. Giving away the name and version

We do not want to be shy so there is no need to hide the name and version of this wonderful connector:

/**
 * {@inheritDoc }
 */
@Override
public String getName()
{
    return "Extended Filesystem Connector";
}

/**
 * {@inheritDoc }
 */
@Override
public String getVersion()
{
    return "2.3.0"; /* I am lazy, so I simply use the TranslationStudio Version */
}

4.3. Adding operations

The three operations will be implemented in their own classes, so it will be easy here:

/**
 * {@inheritDoc }
 */
@Override
public ITranslationOperation getTranslationOperation()
{
    return new TranslationOperation(m_pConfiguration);
}

/**
 * {@inheritDoc }
 */
@Override
public ITranslationStatusQueryOperation getTranslationStatusQueryOperation()
{
    return new TranslationStatusQueryOperation();
}

/**
 * {@inheritDoc }
 */
@Override
public IQueryOperation getQueryOperation(String sQueryData)
{
    return new QueryOperation();
}

4.4. Reacting to configuration changes

This connector does not need to react to configuration changes. However, if your connector wants provide a web interface which has to connect to a third party system, you may want to use this method to store the configuration in your own configuration file. This file may, in turn, be loaded by the web interface when required. For now, we do not need it.

/**
 * {@inheritDoc }
 */
@Override
public void onUpdateConfiguration(String sConfigurationXml)
{
    /* not needed */
}

4.5. Performing additional tasks.

It is possible to perform additional tasks after translatable XMLs have been processed using the ITranslationOperation. This connector, however, does not need this capability.

/**
 * {@inheritDoc }
 */
@Override
public void onPerformAdditionalTasks()
{
    /* not needed */
}

4.6. Interacting with the connector’s configuration panel

The method public IQueryOperation getQueryOperation(String sQueryData) allows your connector’s configuration panel to query data at runtime. For example, if you connect to a third party system and want to load all available projects and list them in the configuration panel, this method is what you are looking for.

The configuration panel can use the method IExchangeCommunicatorsendMessage(String sMessage, IQueryRequestAnswerReceived pOnAnswerCallback); to send a message to an instance of this connector. Importantly, the setup method will not be called. Hence, all data necessary to setup the connector have to be sent in the message itself (i.e. credentials etc.)

This connector does not need such a feature, so we use an empty implementation:

/**
 * Do nothing
 */
private static class QueryOperation implements IQueryOperation
{
    /**
     * {@inheritDoc }
     */
    @Override
    public void query()
    {
    }

    /**
     * {@inheritDoc }
     */
    @NotNull
    @Override
    public String getResult()
    {
        return "";
    }
}

4.7. Processing translatable XMLs

Whenever your connector has to handle new translatable XMLs, an instance of the ITranslationOperation is requested.

This connector has the following features:

  • Send a notification
  • Copy files to another folder
  • Execute a command

First, create a new class TranslationOperation implementing ITranslationOperation

As shown above, the class will get a configuration object and your minimal initial class needs have the following elements:

private static final Logger LOGGER = Logger.getLogger(TranslationOperation.class.getName());

private final List<ITranslatableFile> m_vpTranslatableFiles = new ArrayList<>(0);
private final List<String> m_vsCopiedSuccessfully = new ArrayList<>(0);
private final List<String> m_vsCopiedErrorneous = new ArrayList<>(0);
private final List<String> m_vsRemovable = new ArrayList<>(0);

private boolean m_bKeppOriginalFiles = true;
private final IFileSystemConfiguration m_pConfiguration;

TranslationOperation(@NotNull IFileSystemConfiguration pConfiguration)
{
    m_pConfiguration = pConfiguration;
}

Second, store all files that have to be processed:

/**
 * {@inheritDoc }
 */
@Override
public void addTranslatableFiles(List<ITranslatableFilegt; vpList)
{
    if (vpList == null || vpList.isEmpty())
        return;

    m_vpTranslatableFiles.addAll(vpList);
}

Once all translatable xml files have been added to the operation it can be executed:

/**
 * {@inheritDoc }
 */
@Override
public boolean perform()
{
    boolean bSuccess = true;
    for (ITranslatableFile _file : m_vpTranslatableFiles)
    {
        if (!perform(_file))
            bSuccess = false;
    }

    return bSuccess;
}

/**
 * Process a single file
 *
 * @param pFile
 * @return
 */
private boolean perform(ITranslatableFile pFile)
{
    clearLists();

    final String sProjectId = pFile.getProjectId();
    if (sProjectId == null || sProjectId.isEmpty())
        return false;

    final IFileSystemProject pConfig = m_pConfiguration.getConfiguredProject(sProjectId);

    /* Copy files */
    final boolean bCopy = copyTranslatableFiles(pConfig.copyToTargetDirectory(), pConfig.keepOriginalTranslatableFiles());

    /* remove files */
    removeSourceFiles();

    /* execute shell command */
    final String sCommandResult = new ExecuteShellCommandOperation().perform(pConfig.getCommandLineExecutionString());

    /* send email */
    sendMail(pConfig.getNotificationMail(), sCommandResult);

    clearLists();
    return bCopy;
}

The above will process each file and copy them if necessary, execute a system command and send an email.

However, this tutorial will not consider these features and leave it up to you. Yet, you may access send emails using the TranslationStudio API method TranslationStudio.get().requestCustomEmailService().sendMail(…​), for example:

/**
 * Send email
 * @param sMailTo
 * @return
 */
private boolean sendMail(@Nullable String sMail, @NotNull String sShellCommandResult)
{
    if (sMailTo == null || !sMailTo.contains("@"))
        return false;

    final String sBody = MailSummaryComposer.compose(m_vsCopiedSuccessfully, m_vsCopiedErrorneous, m_vsRemovable, sShellCommandResult);
    return TranslationStudio.get().requestCustomEmailService().sendMail(sMailTo, "TranslationStudio: Translatable File(s) available", sBody, false);
}

Third, implement deprecated methods:

/**
 * {@inheritDoc }
 */
@Override
public List<Long> getSuccessfulJobs()
{
    return new ArrayList<>(0);
}

/**
 * {@inheritDoc }
 */
@NotNull
@Override
public List<Long> getFailedJobs()
{
    return new ArrayList<>(0);
}

Forth, if your connector does or does not require the source files anymore, you may say so.

/**
 * {@inheritDoc }
 */
@Override
public boolean requireSourceTanslatableFiles()
{
    /**
     * the connector manages file removal
     */
    return true;
}

Fifth, if you have to monitor the translation process of the processed files, you may provide information to TranslationStudio. The files will be queried regularly using the ITranslationStatusQueryOperation (not needed here).

/**
 * {@inheritDoc }
 */
@NotNull
@Override
public List<ITransferedFile> getResult()
{
    return new ArrayList<>(0);
}

Sixth, all files which could not be processed and should be processed again can be provided as well (not needed here):

/**
 * {@inheritDoc }
 */
@NotNull
@Override
public List<ITranslatableFile> getTranslatableFilesToRetry()
{
    return new ArrayList<>(0);
}

4.8. Monitoring the translation status

TranslationStudio will check the translation status of all transferred files regularly. Your connector has to implement ITranslationStatusQueryOperation to do this.

This connector does not need to check for the status since it only deploys the XMLs files to the filesystem. Therefore, the following implementation will suffice:

class TranslationStatusQueryOperation implements ITranslationStatusQueryOperation
{
    /**
     * {@inheritDoc }
     */
    @Nullable
    @Override
    public byte[] downloadTranslation(String sProjectId, String sFileId)
    {
        return null;
    }

    /**
     * {@inheritDoc }
     */
    @Override
    public void addFileToMonitor(String sFileId, String sProjectId)
    {
    }

    /**
     * {@inheritDoc }
     */
    @NotNull
    @Override
    public List<IFileStatus> query()
    {
        return new ArrayList<>(0);
    }
}

However, if you need to check the status, you first have to store all files to be monitored in a list in the method void addFileToMonitor(String sFileId, String sProjectId).

This method assumes that your target system that received the XML files returned a unique id for each file, hence the two parameters.

The method List<IFileStatus> query() then querys the status and provides a list of status information with one list entry per added file.

If the a file status equals Status.FINISHED, the file will be downloaded immediately using the method byte[] downloadTranslation(String sProjectId, String sFileId).

The file will be stored in the working/translated directory and imported into FirstSpirit.

If you want to remove a file from the monitoring process (e.g. because it is not available anymore), simple set the status to Status.NOTFOUND.

TranslationStudio will remove the file from the database automatically.

5. Deploying the connector

Deploying a connector consists of 2 steps

  1. Deploying the backend JAR to the TranslationStudio Application’s ./connectors directory.
  2. Installing the frontend FSM module using FirstSpirit’s ServerManager (granting access to all in the module’s configuration).

6. Accessing the connector

To access the connector using TranslationStudio’s configuration panel, please consult the installation manual.

However, to allow for an easy installation, you may want to create your own install xml file. A sample file is provided in the sample connector project under ./src/main/resources/example-connector.xml

<?xml version="1.0" encoding="UTF-8"?>
<connector>
    <name>Example Connector</name>
    <instance>com.idmedia.translationstudio.connector.example.impl.Connector</instance>
    <configurable>com.idmedia.translationstudio.connector.example.configuration.ConnectorConfigurationPanel</configurable>
    <xml-connector></xml-connector>
    <xml-gui></xml-gui>
</connector>

Please leave xml-connector and xml-gui empty.

7. Final words

Congratulations, you have successfully created your own connector.

If you need further information, please do not hesitate open a support ticket.