Difference between revisions of "AMI Custom Java Plugins"

From 3forge Documentation
Jump to navigation Jump to search
Tag: visualeditor
Tag: visualeditor
Line 91: Line 91:
  
 
== Example Java Code ==
 
== Example Java Code ==
'''package''' com.demo;
+
<syntaxhighlight lang="java" line="1">
 +
package com.demo;
  
'''import''' java.util.HashMap;
+
import java.util.HashMap;
 +
import java.util.Map;
  
'''import''' java.util.Map;
+
importcom.f1.ami.amicommon.AmiDatasourceAdapter;
 +
importcom.f1.ami.amicommon.AmiDatasourcePlugin;
 +
importcom.f1.container.ContainerTools;
 +
importcom.f1.utils.PropertyController;
  
'''import'''com.f1.ami.amicommon.AmiDatasourceAdapter;
+
public class TestDatasourcePlugin implements AmiDatasourcePlugin {
 
+
       private static final Map<String, Object> OPERATORS_MAP = new HashMap<String, Object>();
'''import'''com.f1.ami.amicommon.AmiDatasourcePlugin;
+
       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>();
'''import'''com.f1.container.ContainerTools;
+
       static {
 
+
               OPERATORS_MAP.put("eq", "=");
'''import'''com.f1.utils.PropertyController;
+
               OPERATORS_MAP.put("ne", "!=");
 
+
               OPERATORS_MAP.put("lt", "<");
'''public''' '''class''' TestDatasourcePlugin '''implements''' AmiDatasourcePlugin {
+
               OPERATORS_MAP.put("gte", ">=");
 
+
               WHERE_SYNTAX_MAP.put("prefix", "((");
       '''private''' '''static''' '''final''' Map<String, Object> '''''OPERATORS_MAP''''' = '''new''' HashMap<String, Object>();
+
               WHERE_SYNTAX_MAP.put("suffix", "))");
 
+
               WHERE_SYNTAX_MAP.put("join", ") or (");
       '''private''' '''static''' '''final''' Map<String, Object> '''''WHERE_SYNTAX_MAP''''' = '''new''' HashMap<String, Object>();
+
               WHERE_SYNTAX_MAP.put("true", "true");
 
 
       '''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");
 
  
 
       }
 
       }
Line 135: Line 120:
 
       @Override
 
       @Override
  
       '''public''' '''void''' init(ContainerTools tools, PropertyController props) {
+
       public void init(ContainerTools tools, PropertyController props) {
  
 
       }
 
       }
Line 141: Line 126:
 
       @Override
 
       @Override
  
       '''public''' String getPluginId() {
+
       public String getPluginId() {
  
               '''return''' "TestDatasource";
+
               return "TestDatasource";
  
 
       }
 
       }
Line 149: Line 134:
 
       @Override
 
       @Override
  
       '''public''' String getDatasourceDescription() {
+
       public String getDatasourceDescription() {
  
               '''return''' "Test";
+
               return "Test";
  
 
       }
 
       }
Line 157: Line 142:
 
       @Override
 
       @Override
  
       '''public''' AmiDatasourceAdapter createDatasourceAdapter() {
+
       public AmiDatasourceAdapter createDatasourceAdapter() {
  
               '''return''' '''new''' TestDatasourceAdapter();
+
               return new TestDatasourceAdapter();
  
 
       }
 
       }
Line 165: Line 150:
 
       @Override
 
       @Override
  
       '''public''' String getDatasourceIcon() {
+
       public String getDatasourceIcon() {
  
               '''return''' "../../../../resources/test.PNG";
+
               return "../../../../resources/test.PNG";
  
 
       }
 
       }
Line 173: Line 158:
 
       @Override
 
       @Override
  
       '''public''' String getDatasourceQuoteType() {
+
       public String getDatasourceQuoteType() {
  
               '''return''' "\"";
+
               return "\"";
  
 
       }
 
       }
Line 181: Line 166:
 
       @Override
 
       @Override
  
       '''public''' Map<String, Object> getDatasourceOperators() {
+
       public Map<String, Object> getDatasourceOperators() {
  
               '''return''' '''''OPERATORS_MAP''''';
+
               return OPERATORS_MAP;
  
 
       }
 
       }
Line 189: Line 174:
 
       @Override
 
       @Override
  
       '''public''' Map<String, Object> getDatasourceWhereClauseSyntax() {
+
       public Map<String, Object> getDatasourceWhereClauseSyntax() {
  
               '''return''' '''''WHERE_SYNTAX_MAP''''';
+
               return WHERE_SYNTAX_MAP;
  
 
       }
 
       }
Line 197: Line 182:
 
       @Override
 
       @Override
  
       '''public''' Map<String, Object> getDatasourceHelp() {
+
       public Map<String, Object> getDatasourceHelp() {
  
               '''return''' '''''HELP_MAP''''';
+
               return HELP_MAP;
  
 
       }
 
       }
  
}  
+
}
  
'''package''' com.demo;
+
package com.demo;
  
'''import''' java.util.ArrayList;
+
import java.util.ArrayList;
  
'''import''' java.util.List;
+
import java.util.List;
  
'''import'''com.f1.ami.amicommon.AmiDatasourceAdapter;
+
importcom.f1.ami.amicommon.AmiDatasourceAdapter;
  
'''import'''com.f1.ami.amicommon.AmiDatasourceException;
+
importcom.f1.ami.amicommon.AmiDatasourceException;
  
'''import''' com.f1.ami.amicommon.AmiDatasourceTracker;
+
import com.f1.ami.amicommon.AmiDatasourceTracker;
  
'''import''' com.f1.ami.amicommon.AmiServiceLocator;
+
import com.f1.ami.amicommon.AmiServiceLocator;
  
'''import''' com.f1.ami.amicommon.msg.AmiCenterQuery;
+
import com.f1.ami.amicommon.msg.AmiCenterQuery;
  
'''import''' com.f1.ami.amicommon.msg.AmiCenterQueryResult;
+
import com.f1.ami.amicommon.msg.AmiCenterQueryResult;
  
'''import''' com.f1.ami.amicommon.msg.AmiCenterUpload;
+
import com.f1.ami.amicommon.msg.AmiCenterUpload;
  
'''import''' com.f1.ami.amicommon.msg.AmiDatasourceTable;
+
import com.f1.ami.amicommon.msg.AmiDatasourceTable;
  
'''import''' com.f1.base.Columns;
+
import com.f1.base.Columns;
  
'''import''' com.f1.base.Row;
+
import com.f1.base.Row;
  
'''import'''com.f1.container.ContainerTools;
+
importcom.f1.container.ContainerTools;
  
'''import'''com.f1.utils.structs.table.BasicTable;
+
importcom.f1.utils.structs.table.BasicTable;
  
'''public''' '''class''' TestDatasourceAdapter '''implements''' AmiDatasourceAdapter {
+
public class TestDatasourceAdapter implements AmiDatasourceAdapter {
  
       '''private''' ContainerTools tools;
+
       private ContainerTools tools;
  
       '''private''' AmiServiceLocator serviceLocator;
+
       private AmiServiceLocator serviceLocator;
  
 
       @Override
 
       @Override
  
       '''public''' '''void''' init(ContainerTools tools, AmiServiceLocator serviceLocator) '''throws'''AmiDatasourceException {
+
       public void init(ContainerTools tools, AmiServiceLocator serviceLocator) throwsAmiDatasourceException {
  
               '''this'''.tools = tools;
+
               this.tools = tools;
  
               '''this'''.serviceLocator = serviceLocator;
+
               this.serviceLocator = serviceLocator;
  
 
       }
 
       }
Line 253: Line 238:
 
       @Override
 
       @Override
  
       '''public'''List<AmiDatasourceTable> getTables(AmiDatasourceTracker debugSink) '''throws'''AmiDatasourceException {
+
       publicList<AmiDatasourceTable> getTables(AmiDatasourceTracker debugSink) throwsAmiDatasourceException {
  
               List<AmiDatasourceTable> tables = '''new'''ArrayList<AmiDatasourceTable>();
+
               List<AmiDatasourceTable> tables = newArrayList<AmiDatasourceTable>();
  
               AmiDatasourceTable table = tools.nw(AmiDatasourceTable.'''class''');
+
               AmiDatasourceTable table = tools.nw(AmiDatasourceTable.class);
  
 
               table.setCollectionName("master");
 
               table.setCollectionName("master");
Line 267: Line 252:
 
               tables.add(table);
 
               tables.add(table);
  
               '''return''' tables;
+
               return tables;
  
 
       }
 
       }
Line 273: Line 258:
 
       @Override
 
       @Override
  
       '''public'''List<AmiDatasourceTable> getPreviewData(List<AmiDatasourceTable> tables, '''int''' previewCount, AmiDatasourceTracker debugSink) '''throws'''AmiDatasourceException {
+
       publicList<AmiDatasourceTable> getPreviewData(List<AmiDatasourceTable> tables, int previewCount, AmiDatasourceTracker debugSink) throwsAmiDatasourceException {
  
               '''for''' ('''int''' i = 0; i < tables.size(); i++) {
+
               for (int i = 0; i < tables.size(); i++) {
  
 
                       AmiDatasourceTable table = tables.get(i);
 
                       AmiDatasourceTable table = tables.get(i);
  
                       AmiCenterQuery q = tools.nw(AmiCenterQuery.'''class''');
+
                       AmiCenterQuery q = tools.nw(AmiCenterQuery.class);
  
 
                       q.setQuery(table.getCustomQuery());
 
                       q.setQuery(table.getCustomQuery());
Line 285: Line 270:
 
                       q.setLimit(previewCount);
 
                       q.setLimit(previewCount);
  
                       AmiCenterQueryResult rs = tools.nw(AmiCenterQueryResult.'''class''');
+
                       AmiCenterQueryResult rs = tools.nw(AmiCenterQueryResult.class);
  
 
                       processQuery(q, rs, debugSink);
 
                       processQuery(q, rs, debugSink);
Line 291: Line 276:
 
                       List<Columns> results = rs.getTables();
 
                       List<Columns> results = rs.getTables();
  
                       '''if''' (results.size() > 0)
+
                       if (results.size() > 0)
  
 
                               table.setPreviewData(results.get(i));
 
                               table.setPreviewData(results.get(i));
Line 297: Line 282:
 
               }
 
               }
  
               '''return''' tables;
+
               return tables;
  
 
       }
 
       }
Line 303: Line 288:
 
       @Override
 
       @Override
  
       '''public''' AmiServiceLocator getServiceLocator() {
+
       public AmiServiceLocator getServiceLocator() {
  
               '''return''' serviceLocator;
+
               return serviceLocator;
  
 
       }
 
       }
Line 311: Line 296:
 
       @Override
 
       @Override
  
       '''public''' '''void''' processQuery(AmiCenterQuery query, AmiCenterQueryResult resultSink, AmiDatasourceTracker debugSink) '''throws''' AmiDatasourceException {
+
       public void processQuery(AmiCenterQuery query, AmiCenterQueryResult resultSink, AmiDatasourceTracker debugSink) throws AmiDatasourceException {
  
 
               String queryStatement = query.getQuery();
 
               String queryStatement = query.getQuery();
Line 317: Line 302:
 
               // Do something with query statement
 
               // Do something with query statement
  
               List<Columns> result = '''new'''ArrayList<Columns>();
+
               List<Columns> result = newArrayList<Columns>();
  
               BasicTable table = '''new''' BasicTable();
+
               BasicTable table = new BasicTable();
  
 
               String id = "id";
 
               String id = "id";
Line 327: Line 312:
 
               String isPaid = "isPaid";
 
               String isPaid = "isPaid";
  
               table.addColumn(String.'''class''', id);
+
               table.addColumn(String.class, id);
  
               table.addColumn(Integer.'''class''', reputation);
+
               table.addColumn(Integer.class, reputation);
  
               bt.addColumn(Boolean.'''class''', isPaid);
+
               bt.addColumn(Boolean.class, isPaid);
  
               Row row = bt.newRow("superman123", 150, '''true''');
+
               Row row = bt.newRow("superman123", 150, true);
  
 
               bt.getRows().add(row);
 
               bt.getRows().add(row);
  
               Row row2 = bt.newRow("trucker66", 400, '''true''');
+
               Row row2 = bt.newRow("trucker66", 400, true);
  
 
               bt.getRows().add(row2);
 
               bt.getRows().add(row2);
Line 347: Line 332:
 
       @Override
 
       @Override
  
       '''public''' '''boolean''' cancelQuery() {
+
       public boolean cancelQuery() {
  
               '''return''' '''false''';
+
               return false;
  
 
       }
 
       }
Line 355: Line 340:
 
       @Override
 
       @Override
  
       '''public''' '''void''' processUpload(AmiCenterUpload upload, AmiCenterQueryResult resultsSink, AmiDatasourceTracker tracker) '''throws'''AmiDatasourceException {
+
       public void processUpload(AmiCenterUpload upload, AmiCenterQueryResult resultsSink, AmiDatasourceTracker tracker) throwsAmiDatasourceException {
  
               '''throw''' '''new'''AmiDatasourceException(AmiDatasourceException.UNSUPPORTED_OPERATION_ERROR, "Upload to datasource");
+
               throw newAmiDatasourceException(AmiDatasourceException.UNSUPPORTED_OPERATION_ERROR, "Upload to datasource");
  
 
       }
 
       }
  
 
}
 
}
 +
</syntaxhighlight>
  
 
== Example - Configuration ==
 
== Example - Configuration ==
 
ami.datasource.plugins=com.demo.TestDatasourcePlugin
 
ami.datasource.plugins=com.demo.TestDatasourcePlugin

Revision as of 20:58, 3 March 2021

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

  1package com.demo;
  2
  3import java.util.HashMap;
  4import java.util.Map;
  5
  6importcom.f1.ami.amicommon.AmiDatasourceAdapter;
  7importcom.f1.ami.amicommon.AmiDatasourcePlugin;
  8importcom.f1.container.ContainerTools;
  9importcom.f1.utils.PropertyController;
 10
 11public class TestDatasourcePlugin implements AmiDatasourcePlugin {
 12       private static final Map<String, Object> OPERATORS_MAP = new HashMap<String, Object>();
 13       private static final Map<String, Object> WHERE_SYNTAX_MAP = new HashMap<String, Object>();
 14       private static final Map<String, Object> HELP_MAP = new HashMap<String, Object>();
 15       static {
 16               OPERATORS_MAP.put("eq", "=");
 17               OPERATORS_MAP.put("ne", "!=");
 18               OPERATORS_MAP.put("lt", "<");
 19               OPERATORS_MAP.put("gte", ">=");
 20               WHERE_SYNTAX_MAP.put("prefix", "((");
 21               WHERE_SYNTAX_MAP.put("suffix", "))");
 22               WHERE_SYNTAX_MAP.put("join", ") or (");
 23               WHERE_SYNTAX_MAP.put("true", "true");
 24
 25       }
 26
 27       @Override
 28
 29       public void init(ContainerTools tools, PropertyController props) {
 30
 31       }
 32
 33       @Override
 34
 35       public String getPluginId() {
 36
 37               return "TestDatasource";
 38
 39       }
 40
 41       @Override
 42
 43       public String getDatasourceDescription() {
 44
 45               return "Test";
 46
 47       }
 48
 49       @Override
 50
 51       public AmiDatasourceAdapter createDatasourceAdapter() {
 52
 53               return new TestDatasourceAdapter();
 54
 55       }
 56
 57       @Override
 58
 59       public String getDatasourceIcon() {
 60
 61               return "../../../../resources/test.PNG";
 62
 63       }
 64
 65       @Override
 66
 67       public String getDatasourceQuoteType() {
 68
 69               return "\"";
 70
 71       }
 72
 73       @Override
 74
 75       public Map<String, Object> getDatasourceOperators() {
 76
 77               return OPERATORS_MAP;
 78
 79       }
 80
 81       @Override
 82
 83       public Map<String, Object> getDatasourceWhereClauseSyntax() {
 84
 85               return WHERE_SYNTAX_MAP;
 86
 87       }
 88
 89       @Override
 90
 91       public Map<String, Object> getDatasourceHelp() {
 92
 93               return HELP_MAP;
 94
 95       }
 96
 97}
 98
 99package com.demo;
100
101import java.util.ArrayList;
102
103import java.util.List;
104
105importcom.f1.ami.amicommon.AmiDatasourceAdapter;
106
107importcom.f1.ami.amicommon.AmiDatasourceException;
108
109import com.f1.ami.amicommon.AmiDatasourceTracker;
110
111import com.f1.ami.amicommon.AmiServiceLocator;
112
113import com.f1.ami.amicommon.msg.AmiCenterQuery;
114
115import com.f1.ami.amicommon.msg.AmiCenterQueryResult;
116
117import com.f1.ami.amicommon.msg.AmiCenterUpload;
118
119import com.f1.ami.amicommon.msg.AmiDatasourceTable;
120
121import com.f1.base.Columns;
122
123import com.f1.base.Row;
124
125importcom.f1.container.ContainerTools;
126
127importcom.f1.utils.structs.table.BasicTable;
128
129public class TestDatasourceAdapter implements AmiDatasourceAdapter {
130
131       private ContainerTools tools;
132
133       private AmiServiceLocator serviceLocator;
134
135       @Override
136
137       public void init(ContainerTools tools, AmiServiceLocator serviceLocator) throwsAmiDatasourceException {
138
139               this.tools = tools;
140
141               this.serviceLocator = serviceLocator;
142
143       }
144
145       @Override
146
147       publicList<AmiDatasourceTable> getTables(AmiDatasourceTracker debugSink) throwsAmiDatasourceException {
148
149               List<AmiDatasourceTable> tables = newArrayList<AmiDatasourceTable>();
150
151               AmiDatasourceTable table = tools.nw(AmiDatasourceTable.class);
152
153               table.setCollectionName("master");
154
155               table.setName("accounts");
156
157               table.setCustomQuery("SELECT * FROM accounts WHERE ${WHERE}");
158
159               tables.add(table);
160
161               return tables;
162
163       }
164
165       @Override
166
167       publicList<AmiDatasourceTable> getPreviewData(List<AmiDatasourceTable> tables, int previewCount, AmiDatasourceTracker debugSink) throwsAmiDatasourceException {
168
169               for (int i = 0; i < tables.size(); i++) {
170
171                       AmiDatasourceTable table = tables.get(i);
172
173                       AmiCenterQuery q = tools.nw(AmiCenterQuery.class);
174
175                       q.setQuery(table.getCustomQuery());
176
177                       q.setLimit(previewCount);
178
179                       AmiCenterQueryResult rs = tools.nw(AmiCenterQueryResult.class);
180
181                       processQuery(q, rs, debugSink);
182
183                       List<Columns> results = rs.getTables();
184
185                       if (results.size() > 0)
186
187                               table.setPreviewData(results.get(i));
188
189               }
190
191               return tables;
192
193       }
194
195       @Override
196
197       public AmiServiceLocator getServiceLocator() {
198
199               return serviceLocator;
200
201       }
202
203       @Override
204
205       public void processQuery(AmiCenterQuery query, AmiCenterQueryResult resultSink, AmiDatasourceTracker debugSink) throws AmiDatasourceException {
206
207               String queryStatement = query.getQuery();
208
209               // Do something with query statement
210
211               List<Columns> result = newArrayList<Columns>();
212
213               BasicTable table = new BasicTable();
214
215               String id = "id";
216
217               String reputation = "reputation";
218
219               String isPaid = "isPaid";
220
221               table.addColumn(String.class, id);
222
223               table.addColumn(Integer.class, reputation);
224
225               bt.addColumn(Boolean.class, isPaid);
226
227               Row row = bt.newRow("superman123", 150, true);
228
229               bt.getRows().add(row);
230
231               Row row2 = bt.newRow("trucker66", 400, true);
232
233               bt.getRows().add(row2);
234
235               resultSink.setTables(result);
236
237       }
238
239       @Override
240
241       public boolean cancelQuery() {
242
243               return false;
244
245       }
246
247       @Override
248
249       public void processUpload(AmiCenterUpload upload, AmiCenterQueryResult resultsSink, AmiDatasourceTracker tracker) throwsAmiDatasourceException {
250
251               throw newAmiDatasourceException(AmiDatasourceException.UNSUPPORTED_OPERATION_ERROR, "Upload to datasource");
252
253       }
254
255}

Example - Configuration

ami.datasource.plugins=com.demo.TestDatasourcePlugin