Wicket Configuration Panel?

Hi,

What would be the route to add a module configuration page on the gateway?

My module listens on a TCP port, e.g. 8080, and I’d like to make that configurable.

I could make that port a tag provided by the module, but I think a form in the gateway would be more elegant…

Is there any easy way to do this? I read the Programmer’s Guide, but the examples are for HomePage, Status, and System Map panels.

Cheers
Ivan

This is what the OPC-UA module does to add its pages during startup:

private void addMenuNodes() {
	String[] pathToParent = { "xopc" };

	LabelConfigMenuNode header = new LabelConfigMenuNode(pathToParent[0], "xopc.config.menutitle");
	header.setPosition(401);

	context.getConfigMenuModel().addConfigMenuNode(null, header);

	context.getConfigMenuModel().addConfigMenuNode(pathToParent,
			new LinkConfigMenuNode("certificates", "xopc.config.certificates.menutitle", CertificatesTabs.class));

	context.getConfigMenuModel().addConfigMenuNode(pathToParent,
			new LinkConfigMenuNode("settings", "xopc.config.settings.menutitle", SettingsPage.class));

	context.getConfigMenuModel().addConfigMenuNode(pathToParent,
			new LinkConfigMenuNode("devices", "xopc.config.devices.menutitle", ListDevicesPage.class));
}

Also, make sure to remove them during shutdown.

Thanks Kevin,

Now how about the pages themselves, for example SettingsPage. I’m assuming that these classes extend a class that takes care of menus, header/footer, etc.

Are you able to post (part of) the code for SettingsPage, or any of these OPC-UA configuration pages? It would be a great template to follow.

Cheers
Ivan

They just need to extend ConfigPanel, which in turn is just a Wicket Panel, but with a couple additions including a constructor that takes a resource key to fetch the title from.

Awesome, I’ll give it a go!

Also, if this is related to the tag provider that you’re making, and it makes sense to perhaps allow creating more than one instance at a time, you could just make it an Extension Point. When you do that, you define a PersistentRecord which will hold your settings, and then the UI will get generated for you when you create or edit an instance of your provider type.

Anyhow, going the route described above is perfectly correct if you only want one set of settings for a gateway.

Regards,

I think we’ll only have one instance running, but I’ll take a look at the ExtensionPoint route as well.

Thanks for the suggestion!

Colby,

I decided to go the ExtensionPoint route to implement the TagProvider. But I’ve run into a problem.

Here is what I’ve done:
[ul][li]Dummy (empty) implementation of TagProvider interface[/li]
[li]Extended SQLTagProviderType[/li]
[li]Implemented a PersistenRecord for extension settings[/li]
[li]In my GatewayHook, on startup() I addSQLTagProviderType(new MyTagProviderType()), and remove on shutdown()[/li][/ul]

Here is the code for the PersistentRecord:

[code]public class DpwsTagProviderSettingsRecord extends PersistentRecord {

public static final RecordMeta<DpwsTagProviderSettingsRecord> META = new RecordMeta<DpwsTagProviderSettingsRecord>(
		DpwsTagProviderSettingsRecord.class,
		"DpwsTagProviderSettings");

public static final LongField ProviderId = new LongField(META, "ProviderId", SFieldFlags.SPRIMARY_KEY);
public static final ReferenceField<SQLTagProviderRecord> Profile =
	new ReferenceField<SQLTagProviderRecord>(META, SQLTagProviderRecord.META, "Id", ProviderId);	

public static final LongField Port = new LongField(META, "Port", SFieldFlags.SMANDATORY);

@Override
public RecordMeta<?> getMeta() {
	return META;
}

public long getProviderId() {
	return getLong(ProviderId);
}

public long getPort() {
	return getLong(Port);
}

public void setPort(long value) {
	setLong(Port, value);
}

}[/code]

What happens is, when I try to add a new Tag Provider, I select the extension type and on hitting next the server displays an Internal error message. Here is the stack trace:

org.apache.wicket.WicketRuntimeException: Method onFormSubmitted of interface org.apache.wicket.markup.html.form.IFormSubmitListener targeted at component [MarkupContainer [Component id = form]] threw an exception at org.apache.wicket.RequestListenerInterface.invoke(RequestListenerInterface.java:193) at org.apache.wicket.request.target.component.listener.ListenerInterfaceRequestTarget.processEvents(ListenerInterfaceRequestTarget.java:73) at org.apache.wicket.request.AbstractRequestCycleProcessor.processEvents(AbstractRequestCycleProcessor.java:92) at org.apache.wicket.RequestCycle.processEventsAndRespond(RequestCycle.java:1250) at org.apache.wicket.RequestCycle.step(RequestCycle.java:1329) at org.apache.wicket.RequestCycle.steps(RequestCycle.java:1428) at org.apache.wicket.RequestCycle.request(RequestCycle.java:545) at org.apache.wicket.protocol.http.WicketFilter.doGet(WicketFilter.java:479) at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:312) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:286) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:845) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447) at java.lang.Thread.run(Unknown Source) Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.apache.wicket.RequestListenerInterface.invoke(RequestListenerInterface.java:182) ... 20 more Caused by: java.lang.NullPointerException at simpleorm.dataset.SRecordInstance.checkFieldIsAccessible(SRecordInstance.java:226) at simpleorm.dataset.SRecordInstance.setObject(SRecordInstance.java:241) at simpleorm.dataset.SRecordInstance.setObject(SRecordInstance.java:236) at com.inductiveautomation.ignition.gateway.web.components.ExtensionPointPage$1$1.onSubmit(ExtensionPointPage.java:115) at com.inductiveautomation.ignition.gateway.web.components.ExtensionPointChoicePanel$1.onSubmit(ExtensionPointChoicePanel.java:53) at org.apache.wicket.markup.html.form.Form.delegateSubmit(Form.java:1536) at org.apache.wicket.markup.html.form.Form.process(Form.java:925) at org.apache.wicket.markup.html.form.Form.onFormSubmitted(Form.java:887) ... 25 more

So… I’ve either done something wrong in my PersistentRecord (is it not referenced properly to the base SQLTagProvider profile?), or having a dummy TagProvider implementation is breaking things (looks like the exception occurs before the TagProvider is ever instantiated), or I’m missing some registration step (e.g. do I need to add the settings PersistentRecord to the schemaUpdater, or something like that?)

Any insight is appreciated. Or if you have an example of an extension point with additional settings.

Cheers
Ivan

Hi,

The PersistentRecord example in the Programmers Guide (under “Programming for the Gateway”>“Storing data with PersistenceRecords”) is pretty complete - though judging from what you have, you might have already seen that. At any rate, though, it sound like you might be missing the part where you register the record type:

public void setup(GatewayContext context){
context.getSchemaUpdater().updatePersistentRecords(DpwsTagProviderSettingsRecord.META);
}

Let me know if you’re already doing that. Everything else seems to be in order, but I could be overlooking something.

Regards,

So it seems I was doing the ReferenceField wrong, now I copied it from the example and I’m moving forward.

However… something else is missing. I can get as far as creating a new Realtime provider, but I can’t set the provider-specific settings properly. So far I have a “Port” field, defined as a LongField. But in the configuration form, this shows up:

Other
¿DpwsTagProviderSettingsRecord.Profile.Name? [form field]
¿DpwsTagProviderSettingsRecord.Port.Name? [form field]

The Profile.Name option makes me select an existing InternalProvider. I thought the extension would automatically create a new provider?

Later, if I try to edit this provider, the settings fields don’t show. There is an exception reported in the console:

java.lang.NullPointerException at com.inductiveautomation.ignition.gateway.sqltags.SQLTagsManagerImpl.registerTagProvider(SQLTagsManagerImpl.java:275) at com.inductiveautomation.ignition.gateway.sqltags.SQLTagsManagerImpl.registerProvider(SQLTagsManagerImpl.java:650) at com.inductiveautomation.ignition.gateway.sqltags.SQLTagsManagerImpl$RealtimeRecordListener.recordAdded(SQLTagsManagerImpl.java:664) at com.inductiveautomation.ignition.gateway.sqltags.SQLTagsManagerImpl$RealtimeRecordListener.recordAdded(SQLTagsManagerImpl.java:659) at com.inductiveautomation.ignition.gateway.localdb.PersistenceInterfaceImpl.notifyRecordAdded(PersistenceInterfaceImpl.java:174) at com.inductiveautomation.ignition.gateway.redundancy.RedundantPersistenceInterfaceImpl._notifyRecordAdded(RedundantPersistenceInterfaceImpl.java:84) at com.inductiveautomation.ignition.gateway.redundancy.RedundantPersistenceInterfaceImpl$RecordUpdateListener.recordAdded(RedundantPersistenceInterfaceImpl.java:291) at com.inductiveautomation.ignition.gateway.redundancy.RedundantPersistenceInterfaceImpl$RecordAddedMessage.notify(RedundantPersistenceInterfaceImpl.java:366) at com.inductiveautomation.ignition.gateway.redundancy.RedundantPersistenceInterfaceImpl$RecordUpdateListener.receiveCall(RedundantPersistenceInterfaceImpl.java:283) at com.inductiveautomation.ignition.gateway.cluster.QueueableMessageReceiver.receiveCall(QueueableMessageReceiver.java:45) at com.inductiveautomation.ignition.gateway.redundancy.RedundancyManagerImpl.dispatchMessage(RedundancyManagerImpl.java:619) at com.inductiveautomation.ignition.gateway.redundancy.RedundancyManagerImpl$ExecuteTask.run(RedundancyManagerImpl.java:640) at com.inductiveautomation.ignition.common.execution.impl.BasicExecutionEngine$ThrowableCatchingRunnable.run(BasicExecutionEngine.java:526) at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source) at java.util.concurrent.FutureTask.run(Unknown Source) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(Unknown Source) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source)

I then deleted this provider, and tried to generate it again. An error message was posted, saying that the record was dirty. This is the associated exception:

simpleorm.utils.SException$Jdbc: Executing INSERT INTO DpwsTagProviderSettings (ProviderId, Port) VALUES (?, ?) for [DpwsTagProviderSettingsRecord 4 NewRecord Dirty1] at simpleorm.sessionjdbc.SSessionJdbcHelper.flushExecuteUpdate(SSessionJdbcHelper.java:409) at simpleorm.sessionjdbc.SSessionJdbcHelper.flush(SSessionJdbcHelper.java:376) at simpleorm.sessionjdbc.SSessionJdbc.flush(SSessionJdbc.java:425) at simpleorm.sessionjdbc.SSessionJdbc.flush(SSessionJdbc.java:410) at simpleorm.sessionjdbc.SSessionJdbc.commitAndDetachDataSet(SSessionJdbc.java:351) at com.inductiveautomation.ignition.gateway.web.components.RecordEditForm.commitRecords(RecordEditForm.java:423) at com.inductiveautomation.ignition.gateway.web.components.RecordEditForm.onSubmit(RecordEditForm.java:374) at com.inductiveautomation.ignition.gateway.web.components.RecordEditForm.onSubmit(RecordEditForm.java:341) at com.inductiveautomation.ignition.gateway.web.components.RecordEditForm$1.onSubmit(RecordEditForm.java:125) at org.apache.wicket.markup.html.form.Form.delegateSubmit(Form.java:1536) at org.apache.wicket.markup.html.form.Form.process(Form.java:925) at org.apache.wicket.markup.html.form.Form.onFormSubmitted(Form.java:887) at sun.reflect.GeneratedMethodAccessor66.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.apache.wicket.RequestListenerInterface.invoke(RequestListenerInterface.java:182) at org.apache.wicket.request.target.component.listener.ListenerInterfaceRequestTarget.processEvents(ListenerInterfaceRequestTarget.java:73) at org.apache.wicket.request.AbstractRequestCycleProcessor.processEvents(AbstractRequestCycleProcessor.java:92) at org.apache.wicket.RequestCycle.processEventsAndRespond(RequestCycle.java:1250) at org.apache.wicket.RequestCycle.step(RequestCycle.java:1329) at org.apache.wicket.RequestCycle.steps(RequestCycle.java:1428) at org.apache.wicket.RequestCycle.request(RequestCycle.java:545) at org.apache.wicket.protocol.http.WicketFilter.doGet(WicketFilter.java:479) at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:312) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:286) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:845) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447) at java.lang.Thread.run(Unknown Source) Caused by: java.sql.SQLIntegrityConstraintViolationException: integrity constraint violation: unique constraint or index violation; "PK_DPWSTAGPROVIDERSETTINGS" table: "DPWSTAGPROVIDERSETTINGS" at org.hsqldb.jdbc.Util.sqlException(Unknown Source) at org.hsqldb.jdbc.Util.sqlException(Unknown Source) at org.hsqldb.jdbc.JDBCPreparedStatement.fetchResult(Unknown Source) at org.hsqldb.jdbc.JDBCPreparedStatement.executeUpdate(Unknown Source) at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:102) at com.inductiveautomation.ignition.gateway.localdb.hsql.DelegatingDataSource$DelegatingConnection$DelegatingPreparedStatement.executeUpdate(DelegatingDataSource.java:548) at simpleorm.sessionjdbc.SSessionJdbcHelper.flushExecuteUpdate(SSessionJdbcHelper.java:407) ... 35 more Caused by: org.hsqldb.HsqlException: integrity constraint violation: unique constraint or index violation; "PK_DPWSTAGPROVIDERSETTINGS" table: "DPWSTAGPROVIDERSETTINGS" at org.hsqldb.error.Error.error(Unknown Source) at org.hsqldb.Constraint.getException(Unknown Source) at org.hsqldb.index.IndexAVL.insert(Unknown Source) at org.hsqldb.persist.RowStoreAVL.indexRow(Unknown Source) at org.hsqldb.persist.RowStoreAVLDisk.indexRow(Unknown Source) at org.hsqldb.TransactionManager2PL.addInsertAction(Unknown Source) at org.hsqldb.Session.addInsertAction(Unknown Source) at org.hsqldb.Table.insertSingleRow(Unknown Source) at org.hsqldb.StatementDML.insertSingleRow(Unknown Source) at org.hsqldb.StatementInsert.getResult(Unknown Source) at org.hsqldb.StatementDMQL.execute(Unknown Source) at org.hsqldb.Session.executeCompiledStatement(Unknown Source) at org.hsqldb.Session.execute(Unknown Source) ... 40 more

Any clues?

More information: if I now try to delete the InternalProvider to which I had linked my extension provider, I get the following exception:

simpleorm.utils.SException$Jdbc: Executing DELETE FROM SQLTagProvider WHERE SQLTagProvider_ID = ? for [SQLTagProviderRecord 4 Deleted Dirty1] at simpleorm.sessionjdbc.SSessionJdbcHelper.flushExecuteUpdate(SSessionJdbcHelper.java:409) at simpleorm.sessionjdbc.SSessionJdbcHelper.flush(SSessionJdbcHelper.java:376) at simpleorm.sessionjdbc.SSessionJdbc.flush(SSessionJdbc.java:425) at simpleorm.sessionjdbc.SSessionJdbc.flush(SSessionJdbc.java:410) at simpleorm.sessionjdbc.SSessionJdbc.commitAndDetachDataSet(SSessionJdbc.java:351) at com.inductiveautomation.ignition.gateway.web.components.actions.DeleteRecordAction.doDelete(DeleteRecordAction.java:69) at com.inductiveautomation.ignition.gateway.web.components.actions.DeleteRecordAction.execute(DeleteRecordAction.java:148) at com.inductiveautomation.ignition.gateway.web.components.ConfirmationPanel$1.onClick(ConfirmationPanel.java:42) at org.apache.wicket.markup.html.link.Link.onLinkClicked(Link.java:224) at sun.reflect.GeneratedMethodAccessor59.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.apache.wicket.RequestListenerInterface.invoke(RequestListenerInterface.java:182) at org.apache.wicket.request.target.component.listener.ListenerInterfaceRequestTarget.processEvents(ListenerInterfaceRequestTarget.java:73) at org.apache.wicket.request.AbstractRequestCycleProcessor.processEvents(AbstractRequestCycleProcessor.java:92) at org.apache.wicket.RequestCycle.processEventsAndRespond(RequestCycle.java:1250) at org.apache.wicket.RequestCycle.step(RequestCycle.java:1329) at org.apache.wicket.RequestCycle.steps(RequestCycle.java:1428) at org.apache.wicket.RequestCycle.request(RequestCycle.java:545) at org.apache.wicket.protocol.http.WicketFilter.doGet(WicketFilter.java:479) at org.apache.wicket.protocol.http.WicketFilter.doFilter(WicketFilter.java:312) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:286) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:845) at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583) at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447) at java.lang.Thread.run(Unknown Source) Caused by: java.sql.SQLIntegrityConstraintViolationException: integrity constraint violation: foreign key no action; FK_DPWSTAGPROVIDERSETTINGS_ID table: DPWSTAGPROVIDERSETTINGS at org.hsqldb.jdbc.Util.sqlException(Unknown Source) at org.hsqldb.jdbc.Util.sqlException(Unknown Source) at org.hsqldb.jdbc.JDBCPreparedStatement.fetchResult(Unknown Source) at org.hsqldb.jdbc.JDBCPreparedStatement.executeUpdate(Unknown Source) at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:102) at com.inductiveautomation.ignition.gateway.localdb.hsql.DelegatingDataSource$DelegatingConnection$DelegatingPreparedStatement.executeUpdate(DelegatingDataSource.java:548) at simpleorm.sessionjdbc.SSessionJdbcHelper.flushExecuteUpdate(SSessionJdbcHelper.java:407) ... 32 more Caused by: org.hsqldb.HsqlException: integrity constraint violation: foreign key no action; FK_DPWSTAGPROVIDERSETTINGS_ID table: DPWSTAGPROVIDERSETTINGS at org.hsqldb.error.Error.error(Unknown Source) at org.hsqldb.StatementDML.performReferentialActions(Unknown Source) at org.hsqldb.StatementDML.delete(Unknown Source) at org.hsqldb.StatementDML.executeDeleteStatement(Unknown Source) at org.hsqldb.StatementDML.getResult(Unknown Source) at org.hsqldb.StatementDMQL.execute(Unknown Source) at org.hsqldb.Session.executeCompiledStatement(Unknown Source) at org.hsqldb.Session.execute(Unknown Source) ... 37 more

More info: I used the “Advanced” tab to execute some queries on the internal database.

The record for my provider extension was there and seemed correct. The reference to the tag provider was good, and the “Port” value was there too.

I manually deleted this record, and that “unlocked” deleting the provider.

Still, I don’t understand where I went wrong in the first place:

  1. Where do I configure the display names for the “Port” field?
  2. When I create my extension provider, can I automatically create a “base” internal provider, so that I don’t have to build on an existing one?
  3. Why couldn’t I go back and edit those settings? Everything seemed OK in the internal database.
  4. When I deleted my extension provider, the record was not deleted from the internal database. How should I do this?

Thanks for all the support!
Ivan