AMI Custom Java Plugins
Overview
AMI is an extendable platform such that java plugins can be integrated at various touch points throughout the product. There are various types of plugins, each for supporting a particular purpose. Regardless of type, there are certain guidelines to follow when embedding a plugin into AMI: 1. Write a Java class which implements the appropriate interface. a. The various interfaces are discussed in the following sections. b. Each plugin should have a universally unique ID, returned by getPluginId() c. Many plugins operate as "factories" which create instances of class. For example the Datasource Plugin creates Datasource Adapters on demand d. The compiled class(es) must be added to the classpath. This is most easily done by bundling them into a jar and placing the jar in the lib directory. All jars in the lib directory are automatically added to the class path 2. Add the fully qualified Java class name to the appropriate property. a. The name of the property coincides with the type of plugin b. Defaults for a given plugin type are found in the config/defaults.properties file. You should override the property in the config/local.properties file). In all cases, the plugins indirectly implement the AmiPlugin interface. Plugins are instantiated and initialized during startup, such that the failure of a plugin to startup will cause AMI to hard fail on startup. The exact reason for failure can be found in the log files.
Interfacing with Directory Naming Service (AMI One, Center)
Overview
In Enterprise environments, some services cannot be directly identified by a physical destination (ex: host name) and are instead logically identified. In this situation, the organization implements a directory naming service that can map, in realtime, the logical identifier to a physical destination. For AMI to access resources in this scenario, a plugin must be written that interfaces with the directory naming service. Then, when a resource is requested inside AMI, AMI will first ask the Plugin to "resolve" the logical name to a physical one, passing the resolved physical one to the underlying connectors. It's the plugin's responsibility to connect to the naming service and provide an answer in a timely fashion.
Using Multiple Resolvers
Note, that many resolvers can be supplied. The order in which they are defined in the property is the order in which they are visited. Once a resolver plugin says it "canResolve" the identifier, the remaining resolvers are not called.
Default case
If no resolvers plugins are provided, or none of the resolvers "canResolve(...)" a given identifier, then the identifier is considered a physical identifier and passed straight to the connector.
Java interface (see javadoc for details)
com.f1.ami.amicommon.AmiNamingServiceResolver
Property name
ami.naming.service.resolvers=comma_delimited_list_of_fully_qualified_java_class_names
Interfacing with Single Sign on and Entitlements (AMI One, Center, Web)
Overview
When a user attempts to access AMI, first it's necessary to validate the user should be granted access, through a valid user name and password. If the user should be granted, then certain attributes may need to be associated with the user that AMI can use to dictate fine-grained access. There are two different entry points into AMI, each of which can have their own instance of an authentication adapter:
- Frontend Web Interface - When accessing AMI through a browser, first the user must supply a user name and password via the html login page (see property name for front end web access)
- Backend Command line interface - When accessing AMI's in-memory database using the command line interface, first the user must execute the login command, which in turn calls an instance of this plugin (see property name for backend command line access)
AMI Predefined Attributes
- ISADMIN - if true, the user will be logged into the website with admin rights
- ISDEV - if true, the user will be logged into the website with developer rights
- DEFAULT_LAYOUT - if set, this will be the default layout loaded on login
- LAYOUTS - a comma delimited list of regular expressions for layouts that are available
- amivar_some_varname - a variable named user.some_varname of type string is added to the user's session. This has been deprecated, use amiscript.variable.
- amiscript.variable.some_varname - a variable named varname of the supplied type is added to the user's session
- AMIDB_PERMISSIONS - a comma delimieted combination of READ,WRITE,ALTER and EXECUTE which controls permissions for the user when logging in via jdbc or db command line
Java interface (see javadoc for details)
com.f1.ami.web.auth.AmiAuthenticator
Property name for front end web access
ami.auth.plugin.class=fully_qualified_class_name
Property name for backend command line access
ami.db.auth.plugin.class=fully_qualified_class_name
Example - Java Code
package com.demo; import java.util.ArrayList; import java.util.List; importcom.f1.container.ContainerTools; importcom.f1.utils.PropertyController; public class TestAuthenticator implements AmiAuthenticator {
@Override public void init(ContainerTools tools, PropertyController props) { // TODO Auto-generated method stub } @Override public AmiAuthResponse authenticate(String namespace, String location, String user, String password) { final List<AmiAuthAttribute> attributes = new ArrayList<AmiAuthAttribute>(); attributes.add(new BasicAmiAttribute("ISDEV", "true")); attributes.add(new BasicAmiAttribute("ISADMIN", "true")); attributes.add(new BasicAmiAttribute("ami_layout_shared", "default_layout.ami")); return new BasicAmiAuthResponse(AmiAuthResponse.STATUS_OKAY, null, new BasicAmiAuthUser(user, "Jackie", "Davenson", "777-888-9999", "jDavenson@mail.com", "Tire Co.", attributes)); } @Override public String getPluginId() { return "TestAuthenticator"; }
}
Example - Configuration
ami.auth.plugin.class=com.demo.TestAuthenticatorPlugin
Connections to Custom External Datasources (AMI One, Center)
Overview
Connecting to external datasources or systems for accessing and uploading data is at the core of what AMI does. There are dozens of adapters out of the box for well known databases, file formats, etc. Large organizations that have custom databases/storage systems can access them in AMI by implementing a datasource plugin.
Each datasource can optionally support the following functionality:
- Providing a list of available tables
- Providing for a sample of data for a given table
- Running a query (Downloading data into AMI)
- Uploading data from AMI into the datasource
Java interface (see javadoc for details)
com.f1.ami.amicommon.AmiDatasourcePlugin
com.f1.ami.amicommon.AmiDatasourceAdapter
Property name for front end web access
ami.datasource.plugins=comma_delimited_list_of_fully_qualified_java_class_names
Example Java Code
package com.demo;
import java.util.HashMap;
import java.util.Map;
importcom.f1.ami.amicommon.AmiDatasourceAdapter;
importcom.f1.ami.amicommon.AmiDatasourcePlugin;
importcom.f1.container.ContainerTools;
importcom.f1.utils.PropertyController;
public class TestDatasourcePlugin implements AmiDatasourcePlugin {
private static final Map<String, Object> OPERATORS_MAP = new HashMap<String, Object>();
private static final Map<String, Object> WHERE_SYNTAX_MAP = new HashMap<String, Object>();
private static final Map<String, Object> HELP_MAP = new HashMap<String, Object>();
static {
OPERATORS_MAP.put("eq", "=");
OPERATORS_MAP.put("ne", "!=");
OPERATORS_MAP.put("lt", "<");
OPERATORS_MAP.put("gte", ">=");
WHERE_SYNTAX_MAP.put("prefix", "((");
WHERE_SYNTAX_MAP.put("suffix", "))");
WHERE_SYNTAX_MAP.put("join", ") or (");
WHERE_SYNTAX_MAP.put("true", "true");
}
@Override
public void init(ContainerTools tools, PropertyController props) {
}
@Override
public String getPluginId() {
return "TestDatasource";
}
@Override
public String getDatasourceDescription() {
return "Test";
}
@Override
public AmiDatasourceAdapter createDatasourceAdapter() {
return new TestDatasourceAdapter();
}
@Override
public String getDatasourceIcon() {
return "../../../../resources/test.PNG";
}
@Override
public String getDatasourceQuoteType() {
return "\"";
}
@Override
public Map<String, Object> getDatasourceOperators() {
return OPERATORS_MAP;
}
@Override
public Map<String, Object> getDatasourceWhereClauseSyntax() {
return WHERE_SYNTAX_MAP;
}
@Override
public Map<String, Object> getDatasourceHelp() {
return HELP_MAP;
}
}
package com.demo;
import java.util.ArrayList;
import java.util.List;
importcom.f1.ami.amicommon.AmiDatasourceAdapter;
importcom.f1.ami.amicommon.AmiDatasourceException;
import com.f1.ami.amicommon.AmiDatasourceTracker;
import com.f1.ami.amicommon.AmiServiceLocator;
import com.f1.ami.amicommon.msg.AmiCenterQuery;
import com.f1.ami.amicommon.msg.AmiCenterQueryResult;
import com.f1.ami.amicommon.msg.AmiCenterUpload;
import com.f1.ami.amicommon.msg.AmiDatasourceTable;
import com.f1.base.Columns;
import com.f1.base.Row;
importcom.f1.container.ContainerTools;
importcom.f1.utils.structs.table.BasicTable;
public class TestDatasourceAdapter implements AmiDatasourceAdapter {
private ContainerTools tools;
private AmiServiceLocator serviceLocator;
@Override
public void init(ContainerTools tools, AmiServiceLocator serviceLocator) throwsAmiDatasourceException {
this.tools = tools;
this.serviceLocator = serviceLocator;
}
@Override
publicList<AmiDatasourceTable> getTables(AmiDatasourceTracker debugSink) throwsAmiDatasourceException {
List<AmiDatasourceTable> tables = newArrayList<AmiDatasourceTable>();
AmiDatasourceTable table = tools.nw(AmiDatasourceTable.class);
table.setCollectionName("master");
table.setName("accounts");
table.setCustomQuery("SELECT * FROM accounts WHERE ${WHERE}");
tables.add(table);
return tables;
}
@Override
publicList<AmiDatasourceTable> getPreviewData(List<AmiDatasourceTable> tables, int previewCount, AmiDatasourceTracker debugSink) throwsAmiDatasourceException {
for (int i = 0; i < tables.size(); i++) {
AmiDatasourceTable table = tables.get(i);
AmiCenterQuery q = tools.nw(AmiCenterQuery.class);
q.setQuery(table.getCustomQuery());
q.setLimit(previewCount);
AmiCenterQueryResult rs = tools.nw(AmiCenterQueryResult.class);
processQuery(q, rs, debugSink);
List<Columns> results = rs.getTables();
if (results.size() > 0)
table.setPreviewData(results.get(i));
}
return tables;
}
@Override
public AmiServiceLocator getServiceLocator() {
return serviceLocator;
}
@Override
public void processQuery(AmiCenterQuery query, AmiCenterQueryResult resultSink, AmiDatasourceTracker debugSink) throws AmiDatasourceException {
String queryStatement = query.getQuery();
// Do something with query statement
List<Columns> result = newArrayList<Columns>();
BasicTable table = new BasicTable();
String id = "id";
String reputation = "reputation";
String isPaid = "isPaid";
table.addColumn(String.class, id);
table.addColumn(Integer.class, reputation);
bt.addColumn(Boolean.class, isPaid);
Row row = bt.newRow("superman123", 150, true);
bt.getRows().add(row);
Row row2 = bt.newRow("trucker66", 400, true);
bt.getRows().add(row2);
resultSink.setTables(result);
}
@Override
public boolean cancelQuery() {
return false;
}
@Override
public void processUpload(AmiCenterUpload upload, AmiCenterQueryResult resultsSink, AmiDatasourceTracker tracker) throwsAmiDatasourceException {
throw newAmiDatasourceException(AmiDatasourceException.UNSUPPORTED_OPERATION_ERROR, "Upload to datasource");
}
}
Example - Configuration
ami.datasource.plugins=com.demo.TestDatasourcePlugin