Trace:
Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision Next revision Both sides next revision | ||
jvx:code_snippets [2018/02/06 10:09] admin |
jvx:code_snippets [2018/02/06 10:34] admin |
||
---|---|---|---|
Line 1: | Line 1: | ||
~~NOTRANS~~ | ~~NOTRANS~~ | ||
+ | ~~Title: JVx code snippets~~ | ||
We have a list of useful code snippets for you. Simply use them for your application. All snippets are free to use and licensed under [[https://www.apache.org/licenses/LICENSE-2.0|Apache 2.0]]. | We have a list of useful code snippets for you. Simply use them for your application. All snippets are free to use and licensed under [[https://www.apache.org/licenses/LICENSE-2.0|Apache 2.0]]. | ||
Line 7: | Line 8: | ||
Access a DBStorage without JVx Server, Lifecycle Object, Security e.g. for Unit tests | Access a DBStorage without JVx Server, Lifecycle Object, Security e.g. for Unit tests | ||
- | <file java> | + | <file java ContactsAutoFrame.java> |
//configure DB access | //configure DB access | ||
DBAccess dba = new DBAccess(); | DBAccess dba = new DBAccess(); | ||
Line 42: | Line 43: | ||
Sometimes we need an application without overhead, e.g for Vaadin UI. | Sometimes we need an application without overhead, e.g for Vaadin UI. | ||
- | <file java> | + | <file java SimpleApplication.java> |
public class SimpleApplication extends Application | public class SimpleApplication extends Application | ||
implements IExceptionListener | implements IExceptionListener | ||
Line 114: | Line 115: | ||
} // SimpleApplication | } // SimpleApplication | ||
+ | </file> | ||
+ | |||
+ | ==== Test your business logic with JUnit ==== | ||
+ | |||
+ | Tests our business logic without an application server, but with our Lifecycle objects. We test our server code without specific configuration or modifications for unit tests. | ||
+ | |||
+ | Business Object: | ||
+ | |||
+ | <file java UserRegistration.java> | ||
+ | public class UserRegistration | ||
+ | { | ||
+ | /** | ||
+ | * Removes/Unregisters a user and all its content. | ||
+ | * | ||
+ | * @param pUserName the user name | ||
+ | * @throws Exception if the user can not be deleted | ||
+ | */ | ||
+ | public void delete(String pUserName) throws Exception | ||
+ | { | ||
+ | try | ||
+ | { | ||
+ | DBAccess dba = (DBAccess)SessionContext.getCurrentSession().get("newDBAccess"); | ||
+ | |||
+ | DBStorage dbsUser = new DBStorage(); | ||
+ | dbsUser.setDBAccess(dba); | ||
+ | dbsUser.setWritebackTable("USERS"); | ||
+ | dbsUser.open(); | ||
+ | |||
+ | List<IBean> liBeans = dbsUser.fetchBean(new Equals("USERNAME", | ||
+ | pUserName), null, 0, -1); | ||
+ | |||
+ | //the username is unique and it's not possible that a user exists | ||
+ | //more than once! | ||
+ | if (liBeans.size() == 1) | ||
+ | { | ||
+ | //delete the user (db will cascade all user-data) | ||
+ | dbsUser.delete(liBeans.get(0)); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | throw new SecurityException("User '" + pUserName + "' was not found!"); | ||
+ | } | ||
+ | } | ||
+ | catch (Throwable th) | ||
+ | { | ||
+ | throw new SecurityException("It's not possible to delete the user!", th); | ||
+ | } | ||
+ | } | ||
+ | } // UserRegistration | ||
+ | </file> | ||
+ | |||
+ | A standard Lifecycle Object: | ||
+ | |||
+ | <file java Session.java> | ||
+ | public class Session extends GenericBean | ||
+ | { | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // User-defined methods | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** | ||
+ | * Creates a new database access object. | ||
+ | * | ||
+ | * @return the new database access object | ||
+ | * @throws Exception if the connection can not be opened | ||
+ | */ | ||
+ | public DBAccess getNewDBAccess() throws Exception | ||
+ | { | ||
+ | DBAccess dba = DBAccess.getDBAccess(DBSecurityManager.getCredentials( | ||
+ | SessionContext.getCurrentSessionConfig())); | ||
+ | dba.open(); | ||
+ | | ||
+ | return dba; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Returns the access to the database. | ||
+ | * | ||
+ | * @return the access to the database | ||
+ | * @throws Exception if the datasource can not be opened | ||
+ | */ | ||
+ | public DBAccess getDBAccess() throws Exception | ||
+ | { | ||
+ | DBAccess dba = (DBAccess)get("dBAccess"); | ||
+ | | ||
+ | if (dba == null) | ||
+ | { | ||
+ | dba = getNewDBAccess(); | ||
+ | |||
+ | put("dBAccess", dba); | ||
+ | } | ||
+ | | ||
+ | return dba; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Gets the user registration business object. | ||
+ | * | ||
+ | * @return the business object for user registrations | ||
+ | */ | ||
+ | public UserRegistration getRegistration() | ||
+ | { | ||
+ | UserRegistration ureg = (UserRegistration)get("registration"); | ||
+ | | ||
+ | if (ureg == null) | ||
+ | { | ||
+ | ureg = new UserRegistration(); | ||
+ | |||
+ | put("registration", ureg); | ||
+ | } | ||
+ | | ||
+ | return ureg; | ||
+ | } | ||
+ | |||
+ | } // Session | ||
+ | </file> | ||
+ | |||
+ | Unit Test: | ||
+ | |||
+ | <file java> | ||
+ | /** | ||
+ | * Tests the our delete method from the user registration object. | ||
+ | * | ||
+ | * @throws Throwable if the test fails | ||
+ | */ | ||
+ | @Test | ||
+ | public void testDelete() throws Throwable | ||
+ | { | ||
+ | AbstractConnection con = createConnection(null); | ||
+ | |||
+ | try | ||
+ | { | ||
+ | //delete user with the name "unknownuser" | ||
+ | con.call("registration", "delete", "unknownuser"); | ||
+ | | ||
+ | Assert.fail("User 'unknownuser' found!"); | ||
+ | } | ||
+ | catch (SecurityException se) | ||
+ | { | ||
+ | Assert.assertEquals("User 'unknownuser' was not found!", | ||
+ | se.getMessage()); | ||
+ | } | ||
+ | |||
+ | con.close(); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Creates a new connection. | ||
+ | * | ||
+ | * @param pConProps additional connection properties | ||
+ | * @return the connection | ||
+ | * @throws Throwable if the connection can not be opened | ||
+ | */ | ||
+ | private AbstractConnection createConnection(Hashtable<String, String> pConProps) throws Throwable | ||
+ | { | ||
+ | MasterConnection macon = new MasterConnection(new DirectServerConnection()); | ||
+ | macon.setApplicationName("app"); | ||
+ | macon.setUserName("user"); | ||
+ | macon.setPassword("pwd"); | ||
+ | |||
+ | if (pConProps != null) | ||
+ | { | ||
+ | for (Map.Entry<String, String> entry : pConProps.entrySet()) | ||
+ | { | ||
+ | macon.setProperty(entry.getKey(), entry.getValue()); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | macon.open(); | ||
+ | |||
+ | return macon; | ||
+ | } | ||
+ | </file> | ||
+ | |||
+ | ==== A very simple AbstractMemStorage implementation ==== | ||
+ | |||
+ | A server side memory storage with the column names: ID, NAME, PATH. The column PATH is not visible on the client-side, but is important for server-side. If "error" is set as NAME, an Exception is thrown! | ||
+ | |||
+ | <file java SimpleMemStorage.java> | ||
+ | public class SimpleMemStorage extends AbstractMemStorage | ||
+ | { | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Abstract methods implementation | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** | ||
+ | * {@inheritDoc} | ||
+ | */ | ||
+ | @Override | ||
+ | public RowDefinition getRowDefinition() throws ModelException | ||
+ | { | ||
+ | RowDefinition rowdef = new RowDefinition(); | ||
+ | rowdef.addColumnDefinition(new ColumnDefinition("ID", new BigDecimalDataType())); | ||
+ | rowdef.addColumnDefinition(new ColumnDefinition("NAME", new StringDataType())); | ||
+ | rowdef.addColumnDefinition(new ColumnDefinition("PATH", new StringDataType())); | ||
+ | |||
+ | rowdef.setPrimaryKeyColumnNames(new String[] {"ID"}); | ||
+ | | ||
+ | rowdef.setColumnView(null, new ColumnView("ID", "NAME")); | ||
+ | | ||
+ | return rowdef; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * {@inheritDoc} | ||
+ | */ | ||
+ | @Override | ||
+ | public void loadData(MemDataBook pBook, ICondition pFilter) throws ModelException | ||
+ | { | ||
+ | pBook.deleteAllDataRows(); | ||
+ | | ||
+ | pBook.insert(false); | ||
+ | pBook.setValues(new String[] {"ID", "NAME", "PATH"}, | ||
+ | new Object[] {BigDecimal.valueOf(0), "First", "/home/first"}); | ||
+ | pBook.insert(false); | ||
+ | pBook.setValues(new String[] {"ID", "NAME", "PATH"}, | ||
+ | new Object[] {BigDecimal.valueOf(1), "Second", "/home/second"}); | ||
+ | pBook.insert(false); | ||
+ | pBook.setValues(new String[] {"ID", "NAME", "PATH"}, | ||
+ | new Object[] {BigDecimal.valueOf(2), "Third", "/home/third"}); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * {@inheritDoc} | ||
+ | */ | ||
+ | @Override | ||
+ | public void insert(DataBookEvent pEvent) throws ModelException | ||
+ | { | ||
+ | if ("error".equals(pEvent.getChangedDataBook().getValueAsString("NAME"))) | ||
+ | { | ||
+ | throw new ModelException("not allowed"); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void delete(DataBookEvent pEvent) | ||
+ | { | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void update(DataBookEvent pEvent) throws ModelException | ||
+ | { | ||
+ | if ("error".equals(pEvent.getChangedDataBook().getValueAsString("NAME"))) | ||
+ | { | ||
+ | throw new ModelException("not allowed"); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | } // SimpleMemStorage | ||
+ | </file> | ||
+ | |||
+ | ==== Change XML files very fast ==== | ||
+ | |||
+ | Our XML file | ||
+ | |||
+ | <file xml> | ||
+ | <server> | ||
+ | <!-- Test: STARTPORT --> | ||
+ | <startport>2001</startport> | ||
+ | |||
+ | <audio>off</audio> | ||
+ | <serial>COM1</serial> | ||
+ | <domain>JVx</domain> | ||
+ | </server> | ||
+ | </file> | ||
+ | |||
+ | Change it: | ||
+ | |||
+ | <file java> | ||
+ | XmlNode xmnRead = readXml("simple.xml"); | ||
+ | |||
+ | xmnRead.setNode("/server/audio", null); | ||
+ | xmnRead.setNode("/server/domain", "www.sibvisions.com"); | ||
+ | |||
+ | writeXml(xmnRead, "simple.xml"); | ||
+ | </file> | ||
+ | |||
+ | ==== EventHandler without Listener interface ==== | ||
+ | |||
+ | Event definition: | ||
+ | |||
+ | <file java> | ||
+ | /** the event handler for captured (the event has one parameter: byte[]). */ | ||
+ | private CallableHandler chCaptured = new CallableHandler(byte[].class); | ||
+ | |||
+ | /** the event handler for canceled (the event has no parameter). */ | ||
+ | private CallableHandler chCanceled = new CallableHandler(); | ||
+ | </file> | ||
+ | |||
+ | Event access: | ||
+ | |||
+ | <file java> | ||
+ | /** | ||
+ | * Gets the captured event handler. | ||
+ | * | ||
+ | * @return the event handler | ||
+ | */ | ||
+ | public CallableHandler eventCaptured() | ||
+ | { | ||
+ | return chCaptured; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Gets the canceled event handler. | ||
+ | * | ||
+ | * @return the event handler | ||
+ | */ | ||
+ | public CallableHandler eventCanceled() | ||
+ | { | ||
+ | return chCanceled; | ||
+ | } | ||
+ | </file> | ||
+ | |||
+ | Dispatch events: | ||
+ | |||
+ | <file java> | ||
+ | chCaptured.dispatchEvent(byData); | ||
+ | chCancel.dispatchEvent(); | ||
+ | </file> | ||
+ | |||
+ | Listener registration: | ||
+ | |||
+ | <file java> | ||
+ | object.eventCaptured().addListener(this, "doCapture"); | ||
+ | |||
+ | public void doCapture(byte[] pImage) throws Exception | ||
+ | { | ||
+ | //... | ||
+ | } | ||
+ | </file> | ||
+ | |||
+ | ==== EventHandler with Listener interface ==== | ||
+ | |||
+ | The interface: | ||
+ | |||
+ | <file java IRemoteApplicationListener.java> | ||
+ | public interface IRemoteApplicationListener | ||
+ | { | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Method definitions | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** | ||
+ | * Invoked when login was successful. | ||
+ | * | ||
+ | * @param pApplication the application | ||
+ | */ | ||
+ | public void afterLogin(RemoteApplication pApplication); | ||
+ | | ||
+ | /** | ||
+ | * Invoked when logout was successful. | ||
+ | * | ||
+ | * @param pApplication the application | ||
+ | */ | ||
+ | public void afterLogout(RemoteApplication pApplication); | ||
+ | |||
+ | } // IRemoteApplicationListener | ||
+ | </file> | ||
+ | |||
+ | The EventHandler: | ||
+ | |||
+ | <file java RemoteApplicationHandler.java> | ||
+ | public class RemoteApplicationHandler extends RuntimeEventHandler<IRemoteApplicationListener> | ||
+ | { | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Initialization | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** | ||
+ | * Constructs a new RemoteApplicationHandler. | ||
+ | * | ||
+ | * @param pListenerMethodName the method to be called inside the interface. | ||
+ | */ | ||
+ | public RemoteApplicationHandler(String pListenerMethodName) | ||
+ | { | ||
+ | super(IRemoteApplicationListener.class, pListenerMethodName); | ||
+ | } | ||
+ | |||
+ | } // RemoteApplicationHandler | ||
+ | </file> | ||
+ | |||
+ | Event access: | ||
+ | |||
+ | <file java> | ||
+ | /** the "after login" event. */ | ||
+ | private RemoteApplicationHandler eventAfterLogin; | ||
+ | |||
+ | /** the "after logout" event. */ | ||
+ | private RemoteApplicationHandler eventAfterLogout; | ||
+ | |||
+ | |||
+ | /** | ||
+ | * Gets the event handler for the after login event. | ||
+ | * | ||
+ | * @return the event handler | ||
+ | */ | ||
+ | public RemoteApplicationHandler eventAfterLogin() | ||
+ | { | ||
+ | if (eventAfterLogin == null) | ||
+ | { | ||
+ | eventAfterLogin = new RemoteApplicationHandler("afterLogin"); | ||
+ | } | ||
+ | return eventAfterLogin; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Gets the event handler for the after logout event. | ||
+ | * | ||
+ | * @return the event handler | ||
+ | */ | ||
+ | public RemoteApplicationHandler eventAfterLogout() | ||
+ | { | ||
+ | if (eventAfterLogout == null) | ||
+ | { | ||
+ | eventAfterLogout = new RemoteApplicationHandler("afterLogout"); | ||
+ | } | ||
+ | return eventAfterLogout; | ||
+ | } | ||
+ | </file> | ||
+ | |||
+ | Dispatch Events: | ||
+ | |||
+ | <file java> | ||
+ | /** | ||
+ | * Fires the after logout event. | ||
+ | */ | ||
+ | protected void afterLogout() | ||
+ | { | ||
+ | if (eventAfterLogout != null) | ||
+ | { | ||
+ | eventAfterLogout.dispatchEvent(this); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Fires the after login event. | ||
+ | */ | ||
+ | protected void afterLogin() | ||
+ | { | ||
+ | if (eventAfterLogin != null) | ||
+ | { | ||
+ | eventAfterLogin.dispatchEvent(this); | ||
+ | } | ||
+ | } | ||
+ | </file> | ||
+ | |||
+ | Listener registration: | ||
+ | |||
+ | <file java> | ||
+ | app.eventAfterLogin().addListener(this, "doAfterLogin"); | ||
+ | app.eventAfterLogout().addListener(this, "doAfterLogout"); | ||
+ | |||
+ | //We do not need the parameter | ||
+ | public void doAfterLogin() | ||
+ | { | ||
+ | } | ||
+ | |||
+ | //We do not need the parameter | ||
+ | public void doAfterLogout() | ||
+ | { | ||
+ | } | ||
+ | </file> | ||
+ | |||
+ | ==== A Custom AccessController ==== | ||
+ | |||
+ | Create a custom AccessController implementation, e.g.: | ||
+ | |||
+ | <file java AppAccessController.java> | ||
+ | package com.sibvisions.apps.test; | ||
+ | |||
+ | import ... | ||
+ | |||
+ | public class AppAccessController implements IAccessController | ||
+ | { | ||
+ | /** the allowed lifecycle objects. */ | ||
+ | private ArrayUtil<String> auAllowedLCO = null; | ||
+ | |||
+ | /** | ||
+ | * {@inheritDoc} | ||
+ | */ | ||
+ | public boolean isAllowed(String pLifecycleName) | ||
+ | { | ||
+ | //all explicite allowed lifecycle objects are accessible | ||
+ | if (auAllowedLCO != null) | ||
+ | { | ||
+ | return auAllowedLCO.contains(pLifecycleName); | ||
+ | } | ||
+ | | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * {@inheritDoc} | ||
+ | */ | ||
+ | public void addAccess(String pLifecycleName) | ||
+ | { | ||
+ | if (pLifecycleName == null) | ||
+ | { | ||
+ | return; | ||
+ | } | ||
+ | | ||
+ | if (auAllowedLCO == null) | ||
+ | { | ||
+ | auAllowedLCO = new ArrayUtil<String>(); | ||
+ | } | ||
+ | |||
+ | if (!auAllowedLCO.contains(pLifecycleName)) | ||
+ | { | ||
+ | auAllowedLCO.add(pLifecycleName); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </file> | ||
+ | |||
+ | Configure the application: | ||
+ | |||
+ | <file xml config.xml> | ||
+ | <?xml version="1.0" encoding="UTF-8"?> | ||
+ | |||
+ | <application> | ||
+ | <securitymanager> | ||
+ | <class>com.sibvisions.rad.server.security.DBSecurityManager</class> | ||
+ | <accesscontroller>com.sibvisions.apps.test.AppAccessController</accesscontroller> | ||
+ | </securitymanager> | ||
+ | |||
+ | <datasource> | ||
+ | <db name="default"> | ||
+ | <driver>org.hsqldb.jdbcDriver</driver> | ||
+ | <url>jdbc:hsqldb:hsql://localhost/demodb</url> | ||
+ | <username>sa</username> | ||
+ | <password></password> | ||
+ | </db> | ||
+ | </datasource> | ||
+ | </application> | ||
+ | </file> | ||
+ | |||
+ | ==== Remote calls to JVx without UI ==== | ||
+ | |||
+ | JVx is a full stack application framework, but all parts are independent. It is no problem to use only the communication classes or the Swing controls without GenUI. This snippet shows how it is possible to use JVx only on server side. You have full session handling and the client is built with your preferred UI or you client has no UI. | ||
+ | |||
+ | Use JVx on server-side as usual. Integrate it into an application server like Tomcat or use it standalone. | ||
+ | |||
+ | Use the communcation classes to access server objects and actions. A simple object call could be implemented like the following snippet. | ||
+ | |||
+ | Get the source code for a specific class via Server Object: | ||
+ | |||
+ | <file java> | ||
+ | HttpConnection httpcon = new HttpConnection("http://demo.sibvisions.org/showcase/services/Server"); | ||
+ | |||
+ | MasterConnection macon = new MasterConnection(httpcon); | ||
+ | macon.setApplicationName("showcase"); | ||
+ | macon.setUserName("admin"); | ||
+ | macon.setPassword("admin"); | ||
+ | macon.open(); | ||
+ | |||
+ | macon.call("sourceCode", "get", "com.sibvisions.apps.showcase.frames.ChartFrame"); | ||
+ | </file> | ||
+ | |||
+ | Get data from the database: | ||
+ | |||
+ | <file java> | ||
+ | SubConnection subcon = macon.createSubConnection("com.sibvisions.apps.showcase.frames.Contacts"); | ||
+ | subcon.open(); | ||
+ | |||
+ | RemoteDataSource dataSource = new RemoteDataSource(); | ||
+ | dataSource.setConnection(subcon); | ||
+ | dataSource.open(); | ||
+ | | ||
+ | RemoteDataBook rdbContacts = new RemoteDataBook(); | ||
+ | rdbContacts.setDataSource(dataSource); | ||
+ | rdbContacts.setName("contacts"); | ||
+ | rdbContacts.open(); | ||
+ | |||
+ | rdbContacts.fetchAll(); | ||
+ | </file> | ||
+ | |||
+ | Use rdbContacts to insert/update/delete records. | ||
+ | |||
+ | ==== Use JVx Swing controls without JVx UI ==== | ||
+ | |||
+ | JVx's Swing controls are independent of JVx UI. It is no problem to use a table that shows some database or in-memory records. It is also possible to use 3-tier or 2-tier architecture. | ||
+ | |||
+ | === In-Memory data === | ||
+ | |||
+ | Use JVxTable and integrate it in your Swing application. Use it like a standard JTable. | ||
+ | |||
+ | <file java JVxTableTest.java> | ||
+ | public class JVxTableTest extends JFrame | ||
+ | { | ||
+ | public static void main(String[] pArgs) throws Throwable | ||
+ | { | ||
+ | new JVxTableTest(); | ||
+ | } | ||
+ | |||
+ | JVxTableTest() throws Throwable | ||
+ | { | ||
+ | //-------------------------------- | ||
+ | // Data | ||
+ | //-------------------------------- | ||
+ | |||
+ | MemDataBook mdbData = new MemDataBook(); | ||
+ | mdbData.setName("person"); | ||
+ | mdbData.getRowDefinition().addColumnDefinition(new ColumnDefinition("FIRSTNAME")); | ||
+ | mdbData.getRowDefinition().addColumnDefinition(new ColumnDefinition("LASTNAME")); | ||
+ | mdbData.getRowDefinition().addColumnDefinition(new ColumnDefinition("PHONE")); | ||
+ | mdbData.open(); | ||
+ | | ||
+ | mdbData.insert(true); | ||
+ | mdbData.setValues(new String[] {"FIRSTNAME", "LASTNAME", "PHONE"}, | ||
+ | new String[] {"JVx", "rocks", "+43 000 / 1234 567"}); | ||
+ | |||
+ | mdbData.saveAllRows(); | ||
+ | |||
+ | //-------------------------------- | ||
+ | // Swing | ||
+ | //-------------------------------- | ||
+ | | ||
+ | JVxTable table = new JVxTable(); | ||
+ | table.setDataBook(mdbData); | ||
+ | |||
+ | setLayout(new BorderLayout()); | ||
+ | | ||
+ | add(table, BorderLayout.CENTER); | ||
+ | | ||
+ | setPreferredSize(new Dimension(500, 400)); | ||
+ | pack(); | ||
+ | setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | ||
+ | setVisible(true); | ||
+ | } | ||
+ | |||
+ | } // JVxTableTest | ||
+ | </file> | ||
+ | |||
+ | You have all JVx features like link cell editors, image choice editors - in your Swing application! | ||
+ | |||
+ | === 2-tier data (database) === | ||
+ | |||
+ | Replace the MemDataBook with following code: | ||
+ | |||
+ | <file java> | ||
+ | //-------------------------------- | ||
+ | // Database handling | ||
+ | //-------------------------------- | ||
+ | |||
+ | //DB Connection | ||
+ | DBAccess dba = DBAccess.getDBAccess("jdbc:hsqldb:hsql://localhost/db"); | ||
+ | dba.setUsername("sa"); | ||
+ | dba.setPassword(""); | ||
+ | dba.open(); | ||
+ | | ||
+ | //Table access | ||
+ | DBStorage dsFiles = dba.createStorage(); | ||
+ | dsFiles.setWritebackTable("FILES"); | ||
+ | dsFiles.setAutoLinkReference(false); | ||
+ | dsFiles.open(); | ||
+ | | ||
+ | //-------------------------------- | ||
+ | // Communication | ||
+ | //-------------------------------- | ||
+ | |||
+ | //Connection handling | ||
+ | DirectObjectConnection con = new DirectObjectConnection(); | ||
+ | con.put("files", dsFiles); | ||
+ | | ||
+ | MasterConnection macon = new MasterConnection(con); | ||
+ | macon.open(); | ||
+ | | ||
+ | //Connect to the "remote" table | ||
+ | RemoteDataSource rds = new RemoteDataSource(macon); | ||
+ | rds.open(); | ||
+ | |||
+ | RemoteDataBook rdbData = new RemoteDataBook(); | ||
+ | rdbData.setDataSource(rds); | ||
+ | rdbData.setName("files"); | ||
+ | rdbData.open(); | ||
+ | </file> | ||
+ | |||
+ | === 3-tier data (database) === | ||
+ | |||
+ | Remote DBAccess, DBStorage and DirectObjectConnection and replace the MasterConnection with following code: | ||
+ | |||
+ | <file java> | ||
+ | HttpConnection con = new HttpConnection("http://server/app/services/Server"); | ||
+ | | ||
+ | MasterConnection macon = new MasterConnection(con); | ||
+ | macon.setApplicationName("showcase"); | ||
+ | macon.setUserName("admin"); | ||
+ | macon.setPassword("admin"); | ||
+ | macon.open(); | ||
+ | </file> | ||
+ | |||
+ | ==== Example for automatic link cell editors === | ||
+ | |||
+ | The client code: | ||
+ | |||
+ | <file java ContactsAutoFrame.java> | ||
+ | /* | ||
+ | * Copyright 2009 SIB Visions GmbH | ||
+ | * | ||
+ | * Licensed under the Apache License, Version 2.0 (the "License"); you may not | ||
+ | * use this file except in compliance with the License. You may obtain a copy of | ||
+ | * the License at | ||
+ | * | ||
+ | * http://www.apache.org/licenses/LICENSE-2.0 | ||
+ | * | ||
+ | * Unless required by applicable law or agreed to in writing, software | ||
+ | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
+ | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
+ | * License for the specific language governing permissions and limitations under | ||
+ | * the License. | ||
+ | * | ||
+ | * | ||
+ | * History | ||
+ | * | ||
+ | * 27.11.2009 - [HM] - creation | ||
+ | * 22.02.2010 - [JR] - changed column names for auto storages | ||
+ | */ | ||
+ | package com.sibvisions.apps.showcase.frames; | ||
+ | |||
+ | import java.io.ByteArrayOutputStream; | ||
+ | import java.io.IOException; | ||
+ | |||
+ | import javax.rad.genui.UIDimension; | ||
+ | import javax.rad.genui.UIInsets; | ||
+ | import javax.rad.genui.celleditor.UIDateCellEditor; | ||
+ | import javax.rad.genui.celleditor.UIImageViewer; | ||
+ | import javax.rad.genui.celleditor.UINumberCellEditor; | ||
+ | import javax.rad.genui.component.UIButton; | ||
+ | import javax.rad.genui.component.UILabel; | ||
+ | import javax.rad.genui.container.UIGroupPanel; | ||
+ | import javax.rad.genui.container.UIPanel; | ||
+ | import javax.rad.genui.container.UISplitPanel; | ||
+ | import javax.rad.genui.control.UIEditor; | ||
+ | import javax.rad.genui.layout.UIBorderLayout; | ||
+ | import javax.rad.genui.layout.UIFormLayout; | ||
+ | import javax.rad.io.IFileHandle; | ||
+ | import javax.rad.model.ColumnDefinition; | ||
+ | import javax.rad.model.ColumnView; | ||
+ | import javax.rad.model.ModelException; | ||
+ | import javax.rad.model.RowDefinition; | ||
+ | import javax.rad.model.condition.ICondition; | ||
+ | import javax.rad.model.condition.LikeIgnoreCase; | ||
+ | import javax.rad.model.datatype.StringDataType; | ||
+ | import javax.rad.model.reference.ReferenceDefinition; | ||
+ | |||
+ | import com.sibvisions.apps.showcase.Showcase; | ||
+ | import com.sibvisions.apps.showcase.components.NavigationTable; | ||
+ | import com.sibvisions.apps.showcase.components.SourceAccessFrame; | ||
+ | import com.sibvisions.rad.model.mem.DataRow; | ||
+ | import com.sibvisions.rad.model.remote.RemoteDataBook; | ||
+ | import com.sibvisions.rad.model.remote.RemoteDataSource; | ||
+ | import com.sibvisions.util.type.FileUtil; | ||
+ | import com.sibvisions.util.type.ImageUtil; | ||
+ | |||
+ | /** | ||
+ | * The <code>ContactsAutoFrame</code> shows contacts, their detail information | ||
+ | * and all educations. The automatic link celleditors will be detected automatically. It shows | ||
+ | * how less code a developer has to write. You can compare the LoC with the | ||
+ | * {@link ContactsFrame} which handles automatic link celleditors definition manually. | ||
+ | * | ||
+ | * @author Martin Handsteiner | ||
+ | */ | ||
+ | public class ContactsAutoFrame extends SourceAccessFrame | ||
+ | implements IDataScreen | ||
+ | { | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Class members | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** the default image when no image was found. */ | ||
+ | private static final String NO_IMAGE = "/com/sibvisions/apps/showcase/images/nobody.gif"; | ||
+ | |||
+ | |||
+ | /** the DataSource for fetching table data. */ | ||
+ | private RemoteDataSource dataSource = new RemoteDataSource(); | ||
+ | |||
+ | /** storage for contacts. */ | ||
+ | private RemoteDataBook rdbContacts = new RemoteDataBook(); | ||
+ | /** storage for contacts educations. */ | ||
+ | private RemoteDataBook rdbContEduc = new RemoteDataBook(); | ||
+ | |||
+ | /** search row. */ | ||
+ | private DataRow drSearch = null; | ||
+ | |||
+ | /** the frames layout. */ | ||
+ | private UIBorderLayout blThis = new UIBorderLayout(); | ||
+ | /** the split between contacts and details. */ | ||
+ | private UISplitPanel splitMain = new UISplitPanel(); | ||
+ | /** the navigator for contacts. */ | ||
+ | private NavigationTable navContacts; | ||
+ | /** the navigagor for showing educations. */ | ||
+ | private NavigationTable navContEdu; | ||
+ | |||
+ | /** the layout for details. */ | ||
+ | private UIFormLayout flDetails = new UIFormLayout(); | ||
+ | /** the details. */ | ||
+ | private UIPanel panDetails = new UIPanel(); | ||
+ | /** the details. */ | ||
+ | private UIGroupPanel gpanDedails = new UIGroupPanel(); | ||
+ | /** the details. */ | ||
+ | private UIGroupPanel gpanEducations = new UIGroupPanel(); | ||
+ | |||
+ | /** contacts layout. */ | ||
+ | private UIBorderLayout blContacts = new UIBorderLayout(); | ||
+ | /** contacts panel. */ | ||
+ | private UIPanel panContacts = new UIPanel(); | ||
+ | /** search panel. */ | ||
+ | private UIPanel panSearch = new UIPanel(); | ||
+ | |||
+ | /** Label. */ | ||
+ | private UILabel lblSalutation = new UILabel(); | ||
+ | /** Label. */ | ||
+ | private UILabel lblAcademicTitle = new UILabel(); | ||
+ | /** Label. */ | ||
+ | private UILabel lblFirstName = new UILabel(); | ||
+ | /** Label. */ | ||
+ | private UILabel lblLastName = new UILabel(); | ||
+ | /** Label. */ | ||
+ | private UILabel lblStreet = new UILabel(); | ||
+ | /** Label. */ | ||
+ | private UILabel lblNr = new UILabel(); | ||
+ | /** Label. */ | ||
+ | private UILabel lblZip = new UILabel(); | ||
+ | /** Label. */ | ||
+ | private UILabel lblTown = new UILabel(); | ||
+ | /** Label. */ | ||
+ | private UILabel lblCountry = new UILabel(); | ||
+ | /** Label. */ | ||
+ | private UILabel lblBirthday = new UILabel(); | ||
+ | /** Label. */ | ||
+ | private UILabel lblSocialSecurityNr = new UILabel(); | ||
+ | /** Label. */ | ||
+ | private UILabel lblHealthInsurance = new UILabel(); | ||
+ | /** Label. */ | ||
+ | private UILabel lblFilename = new UILabel(); | ||
+ | /** labelSuchen. */ | ||
+ | private UILabel lblSearch = new UILabel(); | ||
+ | |||
+ | /** Editor. */ | ||
+ | private UIEditor edtSalutation = new UIEditor(); | ||
+ | /** Editor. */ | ||
+ | private UIEditor edtAcademicTitle = new UIEditor(); | ||
+ | /** Editor. */ | ||
+ | private UIEditor edtFirstName = new UIEditor(); | ||
+ | /** Editor. */ | ||
+ | private UIEditor edtLastName = new UIEditor(); | ||
+ | /** Editor. */ | ||
+ | private UIEditor edtStreet = new UIEditor(); | ||
+ | /** Editor. */ | ||
+ | private UIEditor editNr = new UIEditor(); | ||
+ | /** Editor. */ | ||
+ | private UIEditor edtZip = new UIEditor(); | ||
+ | /** Editor. */ | ||
+ | private UIEditor edtTown = new UIEditor(); | ||
+ | /** Editor. */ | ||
+ | private UIEditor edtCountry = new UIEditor(); | ||
+ | /** Editor. */ | ||
+ | private UIEditor edtBirthday = new UIEditor(); | ||
+ | /** Editor. */ | ||
+ | private UIEditor edtSocialSecurityNr = new UIEditor(); | ||
+ | /** Editor. */ | ||
+ | private UIEditor edtHealthInsurance = new UIEditor(); | ||
+ | /** Editor. */ | ||
+ | private UIEditor edtFilename = new UIEditor(); | ||
+ | /** editSuchen. */ | ||
+ | private UIEditor edtSearch = new UIEditor(); | ||
+ | |||
+ | /** contact image. */ | ||
+ | private UIEditor icoImage = new UIEditor(); | ||
+ | /** load image button. */ | ||
+ | private UIButton butLoadImage = new UIButton(); | ||
+ | |||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Initialization | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** | ||
+ | * Constructs a new instance of <code>ContactsFrame</code>. | ||
+ | * | ||
+ | * @param pApplication the application. | ||
+ | * @throws Throwable if the initialization throws an error | ||
+ | */ | ||
+ | public ContactsAutoFrame(Showcase pApplication) throws Throwable | ||
+ | { | ||
+ | super(pApplication, "com.sibvisions.apps.showcase.frames.ContactsAuto"); | ||
+ | | ||
+ | initializeModel(); | ||
+ | initializeUI(); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Initializes the model. | ||
+ | * | ||
+ | * @throws Throwable if the initialization throws an error | ||
+ | */ | ||
+ | private void initializeModel() throws Throwable | ||
+ | { | ||
+ | dataSource.setConnection(getConnection()); | ||
+ | dataSource.open(); | ||
+ | | ||
+ | rdbContacts.setDataSource(dataSource); | ||
+ | rdbContacts.setName("contacts"); | ||
+ | rdbContacts.open(); | ||
+ | | ||
+ | //set same labels as in details panel | ||
+ | rdbContacts.getRowDefinition().getColumnDefinition("ACTI_ACADEMIC_TITLE").setLabel("Academic title"); | ||
+ | rdbContacts.getRowDefinition().getColumnDefinition("FIRSTNAME").setLabel("First name"); | ||
+ | rdbContacts.getRowDefinition().getColumnDefinition("LASTNAME").setLabel("Last name"); | ||
+ | rdbContacts.getRowDefinition().getColumnDefinition("ZIP").setLabel("ZIP"); | ||
+ | rdbContacts.getRowDefinition().getColumnDefinition("BIRTHDAY").setLabel("DoB"); | ||
+ | rdbContacts.getRowDefinition().getColumnDefinition("SOCIALSECNR").setLabel("Social security nr"); | ||
+ | rdbContacts.getRowDefinition().getColumnDefinition("HEIN_HEALTH_INSURANCE").setLabel("Health insurance"); | ||
+ | | ||
+ | rdbContEduc.setDataSource(dataSource); | ||
+ | rdbContEduc.setName("contEduc"); | ||
+ | rdbContEduc.setMasterReference(new ReferenceDefinition(new String[] {"CONT_ID"}, rdbContacts, new String[] {"ID"})); | ||
+ | rdbContEduc.open(); | ||
+ | | ||
+ | rdbContEduc.getRowDefinition().setColumnView(null, new ColumnView("EDUC_EDUCATION")); | ||
+ | |||
+ | UIImageViewer imageViewer = new UIImageViewer(); | ||
+ | imageViewer.setDefaultImageName(NO_IMAGE); | ||
+ | |||
+ | rdbContacts.getRowDefinition().getColumnDefinition("FILENAME").setReadOnly(true); | ||
+ | rdbContacts.getRowDefinition().getColumnDefinition("IMAGE").getDataType().setCellEditor(imageViewer); | ||
+ | |||
+ | rdbContacts.getRowDefinition().getColumnDefinition("SOCIALSECNR").getDataType().setCellEditor(new UINumberCellEditor("0000")); | ||
+ | rdbContacts.getRowDefinition().getColumnDefinition("BIRTHDAY").getDataType().setCellEditor(new UIDateCellEditor("dd.MM.yyyy")); | ||
+ | | ||
+ | RowDefinition definition = new RowDefinition(); | ||
+ | definition.addColumnDefinition(new ColumnDefinition("SEARCH", new StringDataType())); | ||
+ | | ||
+ | drSearch = new DataRow(definition); | ||
+ | drSearch.eventValuesChanged().addListener(this, "doFilter"); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Initializes the UI. | ||
+ | * | ||
+ | * @throws Throwable if the initialization throws an error | ||
+ | */ | ||
+ | private void initializeUI() throws Throwable | ||
+ | { | ||
+ | lblSearch.setText("Search"); | ||
+ | edtSearch.setDataRow(drSearch); | ||
+ | edtSearch.setColumnName("SEARCH"); | ||
+ | |||
+ | UIFormLayout layoutSearch = new UIFormLayout(); | ||
+ | | ||
+ | panSearch.setLayout(layoutSearch); | ||
+ | panSearch.add(lblSearch, layoutSearch.getConstraints(0, 0)); | ||
+ | panSearch.add(edtSearch, layoutSearch.getConstraints(1, 0, -1, 0)); | ||
+ | | ||
+ | navContacts = new NavigationTable(getApplication().getLauncher(), getConnection(), rdbContacts); | ||
+ | navContacts.getTable().setAutoResize(false); | ||
+ | | ||
+ | panContacts.setLayout(blContacts); | ||
+ | panContacts.add(panSearch, UIBorderLayout.NORTH); | ||
+ | panContacts.add(navContacts, UIBorderLayout.CENTER); | ||
+ | | ||
+ | navContEdu = new NavigationTable(getApplication().getLauncher(), getConnection(), rdbContEduc); | ||
+ | navContEdu.getTable().setPreferredSize(new UIDimension(150, 150)); | ||
+ | navContEdu.eventNewDetail().addListener(this, "doNewEducations"); | ||
+ | | ||
+ | icoImage.setPreferredSize(new UIDimension(75, 75)); | ||
+ | icoImage.setDataRow(rdbContacts); | ||
+ | icoImage.setColumnName("IMAGE"); | ||
+ | | ||
+ | lblSalutation.setText("Salutation"); | ||
+ | lblAcademicTitle.setText("Academic title"); | ||
+ | lblFirstName.setText("First name"); | ||
+ | lblLastName.setText("Last name"); | ||
+ | lblStreet.setText("Street"); | ||
+ | lblNr.setText("Nr"); | ||
+ | lblZip.setText("ZIP"); | ||
+ | lblTown.setText("Town"); | ||
+ | lblCountry.setText("Country"); | ||
+ | lblBirthday.setText("DoB"); | ||
+ | lblSocialSecurityNr.setText("Social security nr"); | ||
+ | lblHealthInsurance.setText("Health insurance"); | ||
+ | lblFilename.setText("Filename"); | ||
+ | | ||
+ | edtSalutation.setDataRow(rdbContacts); | ||
+ | edtSalutation.setColumnName("SALU_SALUTATION"); | ||
+ | edtSalutation.setPreferredSize(new UIDimension(75, 21)); | ||
+ | edtAcademicTitle.setDataRow(rdbContacts); | ||
+ | edtAcademicTitle.setColumnName("ACTI_ACADEMIC_TITLE"); | ||
+ | edtAcademicTitle.setPreferredSize(new UIDimension(75, 21)); | ||
+ | edtFirstName.setDataRow(rdbContacts); | ||
+ | edtFirstName.setColumnName("FIRSTNAME"); | ||
+ | edtLastName.setDataRow(rdbContacts); | ||
+ | edtLastName.setColumnName("LASTNAME"); | ||
+ | edtStreet.setDataRow(rdbContacts); | ||
+ | edtStreet.setColumnName("STREET"); | ||
+ | editNr.setDataRow(rdbContacts); | ||
+ | editNr.setColumnName("NR"); | ||
+ | edtZip.setDataRow(rdbContacts); | ||
+ | edtZip.setColumnName("ZIP"); | ||
+ | edtTown.setDataRow(rdbContacts); | ||
+ | edtTown.setColumnName("TOWN"); | ||
+ | edtCountry.setDataRow(rdbContacts); | ||
+ | edtCountry.setColumnName("CTRY_COUNTRY"); | ||
+ | edtBirthday.setDataRow(rdbContacts); | ||
+ | edtBirthday.setColumnName("BIRTHDAY"); | ||
+ | edtSocialSecurityNr.setDataRow(rdbContacts); | ||
+ | edtSocialSecurityNr.setColumnName("SOCIALSECNR"); | ||
+ | edtHealthInsurance.setDataRow(rdbContacts); | ||
+ | edtHealthInsurance.setColumnName("HEIN_HEALTH_INSURANCE"); | ||
+ | edtFilename.setDataRow(rdbContacts); | ||
+ | edtFilename.setColumnName("FILENAME"); | ||
+ | |||
+ | butLoadImage.setText("Upload"); | ||
+ | butLoadImage.eventAction().addListener(this, "doUpload"); | ||
+ | butLoadImage.setFocusable(false); | ||
+ | | ||
+ | flDetails.setMargins(new UIInsets(20, 20, 20, 20)); | ||
+ | | ||
+ | gpanDedails.setText("Contact"); | ||
+ | | ||
+ | gpanDedails.setLayout(flDetails); | ||
+ | gpanDedails.add(icoImage, flDetails.getConstraints(0, 0, 1, 7)); | ||
+ | gpanDedails.add(butLoadImage, flDetails.getConstraints(0, 8)); | ||
+ | gpanDedails.add(edtFilename, flDetails.getConstraints(1, 8)); | ||
+ | | ||
+ | flDetails.setHorizontalGap(15); | ||
+ | gpanDedails.add(lblSalutation, flDetails.getConstraints(2, 0)); | ||
+ | flDetails.setHorizontalGap(5); | ||
+ | gpanDedails.add(edtSalutation, flDetails.getConstraints(3, 0)); | ||
+ | gpanDedails.add(lblAcademicTitle, flDetails.getConstraints(2, 1)); | ||
+ | gpanDedails.add(edtAcademicTitle, flDetails.getConstraints(3, 1)); | ||
+ | gpanDedails.add(lblFirstName, flDetails.getConstraints(2, 2)); | ||
+ | gpanDedails.add(edtFirstName, flDetails.getConstraints(3, 2, -1, 2)); | ||
+ | gpanDedails.add(lblLastName, flDetails.getConstraints(2, 3)); | ||
+ | gpanDedails.add(edtLastName, flDetails.getConstraints(3, 3, -1, 3)); | ||
+ | | ||
+ | gpanDedails.add(lblSocialSecurityNr, flDetails.getConstraints(2, 4)); | ||
+ | gpanDedails.add(edtSocialSecurityNr, flDetails.getConstraints(3, 4)); | ||
+ | gpanDedails.add(lblBirthday, flDetails.getConstraints(4, 4)); | ||
+ | gpanDedails.add(edtBirthday, flDetails.getConstraints(5, 4, -1, 4)); | ||
+ | | ||
+ | gpanDedails.add(lblHealthInsurance, flDetails.getConstraints(2, 5)); | ||
+ | gpanDedails.add(edtHealthInsurance, flDetails.getConstraints(3, 5, -1, 5)); | ||
+ | |||
+ | gpanDedails.add(lblStreet, flDetails.getConstraints(2, 6)); | ||
+ | gpanDedails.add(edtStreet, flDetails.getConstraints(3, 6, -3, 6)); | ||
+ | gpanDedails.add(lblNr, flDetails.getConstraints(-2, 6)); | ||
+ | gpanDedails.add(editNr, flDetails.getConstraints(-1, 6)); | ||
+ | gpanDedails.add(lblZip, flDetails.getConstraints(2, 7)); | ||
+ | gpanDedails.add(edtZip, flDetails.getConstraints(3, 7)); | ||
+ | gpanDedails.add(lblTown, flDetails.getConstraints(4, 7)); | ||
+ | gpanDedails.add(edtTown, flDetails.getConstraints(5, 7, -1, 7)); | ||
+ | |||
+ | UIFormLayout layoutSchulung = new UIFormLayout(); | ||
+ | |||
+ | gpanEducations.setText("Schooling"); | ||
+ | gpanEducations.setLayout(layoutSchulung); | ||
+ | gpanEducations.add(navContEdu, layoutSchulung.getConstraints(0, 0, -1, -1)); | ||
+ | | ||
+ | UIFormLayout layout = new UIFormLayout(); | ||
+ | | ||
+ | panDetails.setLayout(layout); | ||
+ | panDetails.add(gpanDedails, layout.getConstraints(0, 0, -1, 0)); | ||
+ | panDetails.add(gpanEducations, layout.getConstraints(0, 1, -1, -1)); | ||
+ | | ||
+ | splitMain.setDividerPosition(250); | ||
+ | splitMain.setDividerAlignment(UISplitPanel.DIVIDER_TOP_LEFT); | ||
+ | splitMain.setFirstComponent(panContacts); | ||
+ | splitMain.setSecondComponent(panDetails); | ||
+ | | ||
+ | setTitle("Automatic Link Editors"); | ||
+ | setLayout(blThis); | ||
+ | add(splitMain, UIBorderLayout.CENTER); | ||
+ | } | ||
+ | |||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Interface implementation | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** | ||
+ | * {@inheritDoc} | ||
+ | */ | ||
+ | public void save() throws ModelException | ||
+ | { | ||
+ | dataSource.saveAllDataBooks(); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * {@inheritDoc} | ||
+ | */ | ||
+ | public void reload() throws ModelException | ||
+ | { | ||
+ | dataSource.reloadAllDataBooks(); | ||
+ | } | ||
+ | |||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // User-defined methods | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** | ||
+ | * Saves the image to the contact. | ||
+ | * | ||
+ | * @param pFileHandle the file. | ||
+ | * @throws Throwable if an error occures. | ||
+ | */ | ||
+ | public void storeFile(IFileHandle pFileHandle) throws Throwable | ||
+ | { | ||
+ | String sFormat = FileUtil.getExtension(pFileHandle.getFileName().toLowerCase()); | ||
+ | | ||
+ | if ("png".equals(sFormat) | ||
+ | || "jpg".equals(sFormat) | ||
+ | || "gif".equals(sFormat)) | ||
+ | { | ||
+ | ByteArrayOutputStream stream = new ByteArrayOutputStream(); | ||
+ | |||
+ | ImageUtil.createScaledImage(pFileHandle.getInputStream(), | ||
+ | 140, | ||
+ | 185, | ||
+ | true, | ||
+ | stream, | ||
+ | sFormat); | ||
+ | |||
+ | stream.close(); | ||
+ | |||
+ | rdbContacts.setValue("FILENAME", pFileHandle.getFileName()); | ||
+ | rdbContacts.setValue("IMAGE", stream.toByteArray()); | ||
+ | |||
+ | try | ||
+ | { | ||
+ | rdbContacts.saveSelectedRow(); | ||
+ | } | ||
+ | catch (Exception pException) | ||
+ | { | ||
+ | // Silent Save of current row. | ||
+ | } | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | throw new IOException("Image format '" + sFormat + "' not supported. Use 'png', 'jpg' or 'gif'!"); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Actions | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** | ||
+ | * Starts the image upload. | ||
+ | * | ||
+ | * @throws Throwable if an error occures. | ||
+ | */ | ||
+ | public void doUpload() throws Throwable | ||
+ | { | ||
+ | if (rdbContacts.getSelectedRow() >= 0) | ||
+ | { | ||
+ | getApplication().getLauncher().getFileHandle(this, "storeFile"); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Opens the educations frame. | ||
+ | * | ||
+ | * @throws Throwable if the educations frame can not be opened | ||
+ | */ | ||
+ | public void doNewEducations() throws Throwable | ||
+ | { | ||
+ | getApplication().openFrame(EducationsFrame.class); | ||
+ | } | ||
+ | |||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Interface implementation | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** | ||
+ | * Searches the contacts with the search text. | ||
+ | * | ||
+ | * @throws ModelException if the search fails | ||
+ | */ | ||
+ | public void doFilter() throws ModelException | ||
+ | { | ||
+ | String suche = (String)drSearch.getValue("SEARCH"); | ||
+ | if (suche == null) | ||
+ | { | ||
+ | rdbContacts.setFilter(null); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | ICondition filter = new LikeIgnoreCase("FIRSTNAME", "*" + suche + "*").or( | ||
+ | new LikeIgnoreCase("LASTNAME", "*" + suche + "*").or( | ||
+ | new LikeIgnoreCase("STREET", "*" + suche + "*").or( | ||
+ | new LikeIgnoreCase("TOWN", "*" + suche + "*")))); | ||
+ | rdbContacts.setFilter(filter); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | } // ContactsAutoFrame | ||
+ | </file> | ||
+ | |||
+ | The server code: | ||
+ | |||
+ | <file java ContactsAuto.java> | ||
+ | /* | ||
+ | * Copyright 2009 SIB Visions GmbH | ||
+ | * | ||
+ | * Licensed under the Apache License, Version 2.0 (the "License"); you may not | ||
+ | * use this file except in compliance with the License. You may obtain a copy of | ||
+ | * the License at | ||
+ | * | ||
+ | * http://www.apache.org/licenses/LICENSE-2.0 | ||
+ | * | ||
+ | * Unless required by applicable law or agreed to in writing, software | ||
+ | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
+ | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
+ | * License for the specific language governing permissions and limitations under | ||
+ | * the License. | ||
+ | * | ||
+ | * | ||
+ | * History | ||
+ | * | ||
+ | * 27.11.2009 - [HM] - creation | ||
+ | */ | ||
+ | package com.sibvisions.apps.showcase.frames; | ||
+ | |||
+ | import com.sibvisions.apps.showcase.Session; | ||
+ | import com.sibvisions.rad.persist.jdbc.DBStorage; | ||
+ | |||
+ | /** | ||
+ | * The <code>ContactsAuto</code> class is the life-cycle object for <code>ContactsAutoFrame</code>. | ||
+ | * | ||
+ | * @author Martin Handsteiner | ||
+ | */ | ||
+ | public class ContactsAuto extends Session | ||
+ | { | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Members | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** storage for contacts. */ | ||
+ | private DBStorage dbsContacts = null; | ||
+ | |||
+ | /** Storage for contacts educations. */ | ||
+ | private DBStorage dbsContEduc = null; | ||
+ | |||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // User-defined methods | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** | ||
+ | * Returns the contacts storage. | ||
+ | * | ||
+ | * @return the Contacts storage | ||
+ | * @throws Exception if the initialization throws an error | ||
+ | */ | ||
+ | public DBStorage getContacts() throws Exception | ||
+ | { | ||
+ | if (dbsContacts == null) | ||
+ | { | ||
+ | dbsContacts = new DBStorage(); | ||
+ | dbsContacts.setDBAccess(getDBAccess()); | ||
+ | dbsContacts.setWritebackTable("CONTACTS"); | ||
+ | dbsContacts.setAutoLinkReference(true); | ||
+ | dbsContacts.open(); | ||
+ | } | ||
+ | | ||
+ | return dbsContacts; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Returns the contacts educations storage. | ||
+ | * | ||
+ | * @return the contacts storage | ||
+ | * @throws Exception if the initialization throws an error | ||
+ | */ | ||
+ | public DBStorage getContEduc() throws Exception | ||
+ | { | ||
+ | if (dbsContEduc == null) | ||
+ | { | ||
+ | dbsContEduc = new DBStorage(); | ||
+ | dbsContEduc.setDBAccess(getDBAccess()); | ||
+ | dbsContEduc.setWritebackTable("CONT_EDUC"); | ||
+ | dbsContEduc.setAutoLinkReference(true); | ||
+ | dbsContEduc.open(); | ||
+ | } | ||
+ | | ||
+ | return dbsContEduc; | ||
+ | } | ||
+ | |||
+ | } // ContactsAuto | ||
+ | </file> | ||
+ | |||
+ | The database script: | ||
+ | |||
+ | <file sql create.sql> | ||
+ | ------------------------------------------------------------------------------- | ||
+ | -- Tables | ||
+ | ------------------------------------------------------------------------------- | ||
+ | |||
+ | create table SALUTATIONS | ||
+ | ( | ||
+ | ID INTEGER IDENTITY, | ||
+ | SALUTATION VARCHAR(200), | ||
+ | constraint SALU_UK unique (SALUTATION) | ||
+ | ) | ||
+ | |||
+ | create table ACADEMICTITLES | ||
+ | ( | ||
+ | ID INTEGER IDENTITY, | ||
+ | ACADEMIC_TITLE VARCHAR(200) not null, | ||
+ | constraint ACTI_UK unique (ACADEMIC_TITLE) | ||
+ | ) | ||
+ | |||
+ | create table COUNTRIES | ||
+ | ( | ||
+ | ID INTEGER IDENTITY, | ||
+ | COUNTRY VARCHAR(200) not null, | ||
+ | EU CHAR(1) default 'N' not null, | ||
+ | constraint CTRY_UK unique (COUNTRY) | ||
+ | ) | ||
+ | |||
+ | create table STATES | ||
+ | ( | ||
+ | ID INTEGER IDENTITY, | ||
+ | CTRY_ID INTEGER not null, | ||
+ | STATE VARCHAR(200) not null, | ||
+ | constraint STAT_UK unique (STATE), | ||
+ | constraint STAT_CTRY_ID_FK foreign key (CTRY_ID) references COUNTRIES (ID) | ||
+ | ) | ||
+ | |||
+ | create table DISTRICTS | ||
+ | ( | ||
+ | ID INTEGER IDENTITY, | ||
+ | STAT_ID INTEGER not null, | ||
+ | DISTRICT VARCHAR(200), | ||
+ | constraint DIST_UK unique (DISTRICT), | ||
+ | constraint DIST_STAT_ID_FK foreign key (STAT_ID) references STATES (ID) | ||
+ | ) | ||
+ | |||
+ | create table HEALTHINSURANCES | ||
+ | ( | ||
+ | ID INTEGER IDENTITY, | ||
+ | HEALTH_INSURANCE VARCHAR(200) not null, | ||
+ | constraint HEIN_UK unique (HEALTH_INSURANCE) | ||
+ | ) | ||
+ | |||
+ | create table EDUCATIONS | ||
+ | ( | ||
+ | ID INTEGER IDENTITY, | ||
+ | EDUCATION VARCHAR(200), | ||
+ | constraint EDUC_UK unique (EDUCATION) | ||
+ | ) | ||
+ | |||
+ | create table CONTACTS | ||
+ | ( | ||
+ | ID INTEGER IDENTITY, | ||
+ | SALU_ID INTEGER, | ||
+ | ACTI_ID INTEGER, | ||
+ | FIRSTNAME VARCHAR(200) not null, | ||
+ | LASTNAME VARCHAR(200) not null, | ||
+ | STREET VARCHAR(200), | ||
+ | NR VARCHAR(200), | ||
+ | ZIP VARCHAR(4), | ||
+ | TOWN VARCHAR(200), | ||
+ | CTRY_ID INTEGER, | ||
+ | BIRTHDAY DATE, | ||
+ | SOCIALSECNR DECIMAL(4), | ||
+ | HEIN_ID INTEGER, | ||
+ | FILENAME VARCHAR(200), | ||
+ | IMAGE BINARY, | ||
+ | constraint CONT_SALU_ID_FK foreign key (SALU_ID) references SALUTATIONS (ID), | ||
+ | constraint CONT_CTRY_ID_FK foreign key (CTRY_ID) references COUNTRIES (ID), | ||
+ | constraint CONT_ACTI_ID_FK foreign key (ACTI_ID) references ACADEMICTITLES (ID), | ||
+ | constraint CONT_HEIN_ID_FK foreign key (HEIN_ID) references HEALTHINSURANCES (ID) | ||
+ | ) | ||
+ | |||
+ | create table CONT_EDUC | ||
+ | ( | ||
+ | ID INTEGER IDENTITY, | ||
+ | CONT_ID INTEGER not null, | ||
+ | EDUC_ID INTEGER not null, | ||
+ | constraint COED_UK unique (CONT_ID, EDUC_ID), | ||
+ | constraint COED_CONT_ID_FK foreign key (CONT_ID) references CONTACTS (ID), | ||
+ | constraint COED_EDUC_ID_FK foreign key (EDUC_ID) references EDUCATIONS (ID) | ||
+ | ) | ||
+ | |||
+ | create table FOLDERS | ||
+ | ( | ||
+ | ID INTEGER IDENTITY, | ||
+ | FOLDER VARCHAR(256) not null, | ||
+ | FOLD_ID INTEGER, | ||
+ | constraint FOLD_UK unique (FOLD_ID, FOLDER), | ||
+ | constraint FOLD_FOLD_ID_FK foreign key (FOLD_ID) references FOLDERS (ID) ON DELETE CASCADE | ||
+ | ) | ||
+ | |||
+ | create table FILES | ||
+ | ( | ||
+ | ID INTEGER IDENTITY, | ||
+ | TYPE VARCHAR(50) not null, | ||
+ | FILENAME VARCHAR(256) not null, | ||
+ | FILESIZE INTEGER not null, | ||
+ | CREATED DATE not null, | ||
+ | CREATED_BY VARCHAR(64), | ||
+ | CHANGED DATE not null, | ||
+ | CHANGED_BY VARCHAR(64), | ||
+ | FOLD_ID INTEGER, | ||
+ | constraint FILE_UK unique (FOLD_ID, FILENAME), | ||
+ | constraint FILE_FOLD_ID_FK foreign key (FOLD_ID) references FOLDERS (ID) ON DELETE CASCADE | ||
+ | ) | ||
+ | |||
+ | ------------------------------------------------------------------------------- | ||
+ | -- Views | ||
+ | ------------------------------------------------------------------------------- | ||
+ | |||
+ | CREATE VIEW V_COUNTRIES AS | ||
+ | select c.ID | ||
+ | ,c.COUNTRY | ||
+ | ,c.EU | ||
+ | from COUNTRIES c | ||
+ | order by c.COUNTRY | ||
+ | |||
+ | CREATE VIEW V_STATES AS | ||
+ | select s.ID | ||
+ | ,s.CTRY_ID | ||
+ | ,s.STATE | ||
+ | from STATES s | ||
+ | order by s.STATE | ||
+ | |||
+ | CREATE VIEW V_DISTRICTS AS | ||
+ | select d.ID | ||
+ | ,d.STAT_ID | ||
+ | ,d.DISTRICT | ||
+ | from DISTRICTS d | ||
+ | order by d.DISTRICT | ||
+ | |||
+ | CREATE VIEW V_EDUCATIONS AS | ||
+ | select e.ID | ||
+ | ,e.EDUCATION | ||
+ | from EDUCATIONS e | ||
+ | order by e.EDUCATION | ||
+ | |||
+ | CREATE VIEW V_CONTACTS AS | ||
+ | select C.ID | ||
+ | ,C.SALU_ID | ||
+ | ,(select S.SALUTATION from SALUTATIONS S where S.ID = C.SALU_ID) SALUTATION | ||
+ | ,C.ACTI_ID | ||
+ | ,(select T.ACADEMIC_TITLE from ACADEMICTITLES T where T.ID = C.ACTI_ID) ACADEMIC_TITLE | ||
+ | ,C.FIRSTNAME | ||
+ | ,C.LASTNAME | ||
+ | ,C.STREET | ||
+ | ,C.NR | ||
+ | ,C.ZIP | ||
+ | ,C.TOWN | ||
+ | ,C.CTRY_ID | ||
+ | ,(select CO.COUNTRY from COUNTRIES CO where CO.ID = C.CTRY_ID) COUNTRY | ||
+ | ,C.BIRTHDAY | ||
+ | ,C.SOCIALSECNR | ||
+ | ,C.HEIN_ID | ||
+ | ,(select I.HEALTH_INSURANCE from HEALTHINSURANCES I where I.ID = C.HEIN_ID) HEALTH_INSURANCE | ||
+ | ,C.FILENAME | ||
+ | ,C.IMAGE | ||
+ | from CONTACTS C | ||
+ | |||
+ | CREATE VIEW V_CONT_EDUC AS | ||
+ | select CE.ID | ||
+ | ,CE.CONT_ID | ||
+ | ,CE.EDUC_ID | ||
+ | ,(select E.EDUCATION from EDUCATIONS E where E.ID = CE.EDUC_ID) EDUCATION | ||
+ | from CONT_EDUC CE | ||
+ | </file> | ||
+ | |||
+ | ==== Encrypt passwords ==== | ||
+ | |||
+ | We don't encrypt passwords on client-side and not in the database. We use our middleware for that. It's super easy with server-side triggers/events: | ||
+ | |||
+ | Add the method: | ||
+ | |||
+ | <file java> | ||
+ | /** | ||
+ | * Encrypts a password, if password is changed. | ||
+ | * | ||
+ | * @param pEvent the storage event | ||
+ | * @throws Exception if encryption or data change fails | ||
+ | */ | ||
+ | public void doEncryptPwd(StorageEvent pEvent) throws Exception | ||
+ | { | ||
+ | IBean bn = pEvent.getNew(); | ||
+ | |||
+ | String sNew = (String)bn.get("PASSWORD"); | ||
+ | String sOld; | ||
+ | |||
+ | IBean bnOld = pEvent.getOld(); | ||
+ | |||
+ | if (bnOld != null) | ||
+ | { | ||
+ | sOld = (String)bnOld.get("PASSWORD"); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | sOld = null; | ||
+ | } | ||
+ | |||
+ | if (!CommonUtil.equals(sOld, sNew)) | ||
+ | { | ||
+ | //use the configuration of the selected application! | ||
+ | bn.put("PASSWORD", AbstractSecurityManager.getEncryptedPassword( | ||
+ | SessionContext.getCurrentSession().getConfig(), sNew)); | ||
+ | } | ||
+ | } | ||
+ | </file> | ||
+ | |||
+ | to your life-cycle object e.g. Session.java. | ||
+ | |||
+ | Add an event to your storage | ||
+ | |||
+ | <file java> | ||
+ | //example storage | ||
+ | dbsUser = new DBStorage(); | ||
+ | dbsUser.setDBAccess(getDBAccess()); | ||
+ | dbsUser.setFromClause("USER"); | ||
+ | dbsUser.open(); | ||
+ | |||
+ | dbsUser.eventBeforeInsert().addListener(this, "doEncryptPwd"); | ||
+ | dbsUser.eventBeforeUpdate().addListener(this, "doEncryptPwd"); | ||
+ | </file> | ||
+ | |||
+ | As last step, you need the password algorithm in your config.xml: | ||
+ | |||
+ | <file xml> | ||
+ | <?xml version="1.0" encoding="UTF-8"?> | ||
+ | |||
+ | <application> | ||
+ | <securitymanager> | ||
+ | <passwordalgorithm>SHA</passwordalgorithm> | ||
+ | </securitymanager> | ||
+ | ... | ||
+ | </application> | ||
+ | </file> | ||
+ | |||
+ | Choose one of the following algorithm: MD2, MD4, MD5, SHA, SHA-256, SHA-384, SHA-512. | ||
+ | |||
+ | ==== Fetch data with DBStorage ==== | ||
+ | |||
+ | Sometimes you want to use DBAccess and DBStorage without JVx server and life-cycle objects. This is very easy because JVx was designed as library. Simply use DBStorage and DBAccess in your Servlets, applications, ... | ||
+ | |||
+ | The following example uses DBAccess and DBStorage to check if a user exists in the database and if an email address doesn't exist. | ||
+ | |||
+ | <file java> | ||
+ | dba = DBAccess.getDBAccess("jdbc:oracle:thin:@localhost:1521:xe"); | ||
+ | dba.setUsername("user"); | ||
+ | dba.setPassword("password"); | ||
+ | //also possible, but needs rad/apps/myapp/config.xml | ||
+ | //DBAccess dba = DBAccess.getDBAccess(DBSecurityManager.getCredentials( | ||
+ | // Configuration.getApplicationZone("myapp").getConfig())); | ||
+ | dba.open(); | ||
+ | dba.getConnection().setAutoCommit(false); | ||
+ | |||
+ | DBStorage dbsUser = new DBStorage(); | ||
+ | dbsUser.setDBAccess(dba); | ||
+ | dbsUser.setWritebackTable("USERS"); | ||
+ | dbsUser.open(); | ||
+ | </file> | ||
+ | |||
+ | If you prefer POJOs: | ||
+ | |||
+ | <file java> | ||
+ | User user = dbsUser.createPOJO(User.class, bnUser); | ||
+ | </file> | ||
+ | |||
+ | ==== 50% width with FormLayout ==== | ||
+ | |||
+ | If you have two components, e.g. GroupPanels and if you want that each group is 50% of the parent width, you could use following code: | ||
+ | |||
+ | <file java> | ||
+ | UIFormLayout layout = new UIFormLayout(); | ||
+ | | ||
+ | IConstraints center = layout.getHCenterConstraints(0, 5, -1, 5); | ||
+ | |||
+ | UILabel label = new UILabel(); | ||
+ | label.setPreferredSize(5, 0); | ||
+ | |||
+ | panel.add(label, center); | ||
+ | panel.add(gpanLeft, layout.getConstraints(center.getTopAnchor(), | ||
+ | layout.createAnchor(layout.getLeftMarginAnchor(), 0), | ||
+ | center.getBottomAnchor(), | ||
+ | layout.createAnchor(center.getLeftAnchor(), 0))); | ||
+ | panel.add(gpanRight, layout.getConstraints(center.getTopAnchor(), | ||
+ | layout.createAnchor(center.getRightAnchor(), 0), | ||
+ | center.getBottomAnchor(), | ||
+ | layout.createAnchor(layout.getRightMarginAnchor(), 0))); | ||
+ | </file> | ||
+ | |||
+ | ==== Search column in condition ==== | ||
+ | |||
+ | If you create your own IStorage impelementation, it could be useful to know how to find the value for a specific column: | ||
+ | |||
+ | <file java> | ||
+ | public static Object getEqualsValue(ICondition pFilter, String pColumn) | ||
+ | { | ||
+ | if (pFilter instanceof OperatorCondition) | ||
+ | { | ||
+ | for (ICondition cond : ((OperatorCondition)pFilter).getConditions()) | ||
+ | { | ||
+ | if (cond instanceof Equals) | ||
+ | { | ||
+ | if (pColumn.equals(((Equals)cond).getColumnName())) | ||
+ | { | ||
+ | return ((Equals)cond).getValue(); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | else if (pFilter instanceof Equals) | ||
+ | { | ||
+ | if (pColumn.equals(((Equals)pFilter).getColumnName())) | ||
+ | { | ||
+ | return ((Equals)pFilter).getValue(); | ||
+ | } | ||
+ | } | ||
+ | | ||
+ | return null; | ||
+ | } | ||
+ | </file> | ||
+ | |||
+ | Above method tries to find the first Equals condition with the given column name. | ||
+ | |||
+ | The method doesn't work with recursion because usually this is not necessary. If you want to know the different condition types, simply check ''toString()'' of ''javax.rad.model.condition.BaseCondition''. | ||
+ | |||
+ | ==== Connection property changed listener ==== | ||
+ | |||
+ | It's easy to listen on connection property changed events: | ||
+ | |||
+ | <file java> | ||
+ | MasterConnection appcon = new MasterConnection(createConnection()); | ||
+ | appcon.addPropertyChangedListener("Application.mode", new IConnectionPropertyChangedListener() | ||
+ | { | ||
+ | public void propertyChanged(PropertyEvent pEvent) | ||
+ | { | ||
+ | liProps.add(pEvent.getPropertyName() + " = " + pEvent.getNewValue()); | ||
+ | } | ||
+ | }); | ||
+ | </file> | ||
+ | |||
+ | Above listener will be invoked whenever the connection property **Application.mode** will be changed. | ||
+ | |||
+ | It's also possible to listen on all properties, with following snippet: | ||
+ | |||
+ | <file java> | ||
+ | MasterConnection appcon = new MasterConnection(createConnection()); | ||
+ | appcon.addPropertyChangedListener(null, new IConnectionPropertyChangedListener() | ||
+ | { | ||
+ | public void propertyChanged(PropertyEvent pEvent) | ||
+ | { | ||
+ | liProps.add(pEvent.getPropertyName() + " = " + pEvent.getNewValue()); | ||
+ | } | ||
+ | }); | ||
+ | </file> | ||
+ | |||
+ | ==== Test UI with simple JUnit Test ==== | ||
+ | |||
+ | Sometimes you want a JUnit test case for a specific UI feature. If you need an application frame for your test, you could use following implementation: | ||
+ | |||
+ | A simple Test Launcher: | ||
+ | |||
+ | <file java SimpleTestLauncher.java> | ||
+ | /* | ||
+ | * Copyright 2016 SIB Visions GmbH | ||
+ | * | ||
+ | * Licensed under the Apache License, Version 2.0 (the "License"); you may not | ||
+ | * use this file except in compliance with the License. You may obtain a copy of | ||
+ | * the License at | ||
+ | * | ||
+ | * http://www.apache.org/licenses/LICENSE-2.0 | ||
+ | * | ||
+ | * Unless required by applicable law or agreed to in writing, software | ||
+ | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
+ | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
+ | * License for the specific language governing permissions and limitations under | ||
+ | * the License. | ||
+ | * | ||
+ | * | ||
+ | * History | ||
+ | * | ||
+ | * 11.08.2016 - [JR] - creation | ||
+ | */ | ||
+ | package javax.rad.application; | ||
+ | |||
+ | import javax.rad.ui.IComponent; | ||
+ | import javax.rad.ui.container.IDesktopPanel; | ||
+ | import javax.rad.util.IRunnable; | ||
+ | import javax.swing.JComponent; | ||
+ | import javax.swing.SwingUtilities; | ||
+ | |||
+ | import com.sibvisions.rad.application.Application; | ||
+ | import com.sibvisions.rad.ui.swing.impl.SwingApplication; | ||
+ | |||
+ | /** | ||
+ | * The <code>SimpleTestLauncher</code> starts a {@link SwingApplication} and shows | ||
+ | * a specific content in the application pane. | ||
+ | * | ||
+ | * @author René Jahn | ||
+ | */ | ||
+ | public class SimpleTestLauncher | ||
+ | { | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Initialization | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | | ||
+ | /** | ||
+ | * Creates a new instance of <code>SimpleTestLauncher</code>. | ||
+ | * | ||
+ | * @param pComponent the component to show | ||
+ | */ | ||
+ | public SimpleTestLauncher(final IComponent pComponent) | ||
+ | { | ||
+ | final SwingApplication app = new SwingApplication(); | ||
+ | | ||
+ | app.startup(SimpleTestApplication.class.getName(), null, null); | ||
+ | app.setSystemExitOnDispose(false); | ||
+ | app.eventWindowClosed().addListener(new IRunnable() | ||
+ | { | ||
+ | public void run() | ||
+ | { | ||
+ | synchronized (SimpleTestLauncher.this) | ||
+ | { | ||
+ | SimpleTestLauncher.this.notify(); | ||
+ | } | ||
+ | } | ||
+ | }); | ||
+ | | ||
+ | SwingUtilities.invokeLater(new Runnable() | ||
+ | { | ||
+ | public void run() | ||
+ | { | ||
+ | IDesktopPanel desktop = ((Application)app.getApplication()).getDesktopPane(); | ||
+ | desktop.removeAll(); | ||
+ | desktop.add(pComponent); | ||
+ | | ||
+ | //because test was created before LaF was set! | ||
+ | SwingUtilities.updateComponentTreeUI((JComponent)pComponent.getResource()); | ||
+ | } | ||
+ | }); | ||
+ | | ||
+ | synchronized(this) | ||
+ | { | ||
+ | try | ||
+ | { | ||
+ | wait(); | ||
+ | } | ||
+ | catch (InterruptedException ie) | ||
+ | { | ||
+ | //ignore | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | } // SimpleTestLauncher | ||
+ | </file> | ||
+ | |||
+ | This class wrapps application creation, because we need to set the UI component after the application was started! | ||
+ | |||
+ | The second class is our Application: | ||
+ | |||
+ | <file java SimpleTestApplication.java> | ||
+ | /* | ||
+ | * Copyright 2016 SIB Visions GmbH | ||
+ | * | ||
+ | * Licensed under the Apache License, Version 2.0 (the "License"); you may not | ||
+ | * use this file except in compliance with the License. You may obtain a copy of | ||
+ | * the License at | ||
+ | * | ||
+ | * http://www.apache.org/licenses/LICENSE-2.0 | ||
+ | * | ||
+ | * Unless required by applicable law or agreed to in writing, software | ||
+ | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
+ | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
+ | * License for the specific language governing permissions and limitations under | ||
+ | * the License. | ||
+ | * | ||
+ | * | ||
+ | * History | ||
+ | * | ||
+ | * 11.08.2016 - [JR] - creation | ||
+ | */ | ||
+ | package javax.rad.application; | ||
+ | |||
+ | import javax.rad.application.genui.UILauncher; | ||
+ | import javax.rad.remote.IConnection; | ||
+ | |||
+ | import com.sibvisions.rad.application.Application; | ||
+ | |||
+ | /** | ||
+ | * The <code>SimpleTestApplication</code> is an always connected application | ||
+ | * implementation, for test cases. | ||
+ | * | ||
+ | * @author René Jahn | ||
+ | */ | ||
+ | public class SimpleTestApplication extends Application | ||
+ | { | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Initialization | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** | ||
+ | * Creates a new instance of <code>SimpleTestApplication</code>. | ||
+ | * | ||
+ | * @param pLauncher the launcher | ||
+ | */ | ||
+ | public SimpleTestApplication(UILauncher pLauncher) | ||
+ | { | ||
+ | super(pLauncher); | ||
+ | } | ||
+ | | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Overwritten methods | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | @Override | ||
+ | protected String getApplicationName() | ||
+ | { | ||
+ | return "Test Application"; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | protected IConnection createConnection() throws Exception | ||
+ | { | ||
+ | return null; | ||
+ | } | ||
+ | | ||
+ | @Override | ||
+ | public void notifyVisible() | ||
+ | { | ||
+ | } | ||
+ | | ||
+ | @Override | ||
+ | protected void afterLogin() | ||
+ | { | ||
+ | } | ||
+ | |||
+ | } // SimpleTestApplication | ||
+ | </file> | ||
+ | |||
+ | The application removes login screen and you can use it without manual tasks. | ||
+ | |||
+ | Finally, our JUnit test case: | ||
+ | |||
+ | <file java TestActivity.java> | ||
+ | /* | ||
+ | * Copyright 2016 SIB Visions GmbH | ||
+ | * | ||
+ | * Licensed under the Apache License, Version 2.0 (the "License"); you may not | ||
+ | * use this file except in compliance with the License. You may obtain a copy of | ||
+ | * the License at | ||
+ | * | ||
+ | * http://www.apache.org/licenses/LICENSE-2.0 | ||
+ | * | ||
+ | * Unless required by applicable law or agreed to in writing, software | ||
+ | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
+ | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
+ | * License for the specific language governing permissions and limitations under | ||
+ | * the License. | ||
+ | * | ||
+ | * | ||
+ | * History | ||
+ | * | ||
+ | * 11.08.2016 - [JR] - creation | ||
+ | */ | ||
+ | package com.sibvisions.forum; | ||
+ | |||
+ | import javax.rad.application.SimpleTestLauncher; | ||
+ | import javax.rad.genui.UIFactoryManager; | ||
+ | import javax.rad.genui.container.UIPanel; | ||
+ | import javax.rad.genui.layout.UIBorderLayout; | ||
+ | import javax.rad.model.reference.ReferenceDefinition; | ||
+ | |||
+ | import org.junit.BeforeClass; | ||
+ | import org.junit.Test; | ||
+ | |||
+ | import com.sibvisions.apps.components.NavigationTable; | ||
+ | import com.sibvisions.rad.persist.StorageDataBook; | ||
+ | import com.sibvisions.rad.persist.jdbc.DBAccess; | ||
+ | import com.sibvisions.rad.persist.jdbc.DBStorage; | ||
+ | import com.sibvisions.rad.ui.swing.impl.SwingFactory; | ||
+ | |||
+ | public class TestActivity extends UIPanel | ||
+ | { | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Initialization | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** | ||
+ | * Initializes the unit test. | ||
+ | * | ||
+ | * @throws Exception if initialization fails | ||
+ | */ | ||
+ | @BeforeClass | ||
+ | public static void beforeClass() | ||
+ | { | ||
+ | UIFactoryManager.getFactoryInstance(SwingFactory.class); | ||
+ | } | ||
+ | | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | // Test methods | ||
+ | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
+ | |||
+ | /** | ||
+ | * Tests application start with a custom UI. | ||
+ | * | ||
+ | * @throws Exception if app start fails | ||
+ | */ | ||
+ | @Test | ||
+ | public void testStartApplication() throws Exception | ||
+ | { | ||
+ | DBAccess dba = DBAccess.getDBAccess("jdbc:oracle:thin:@localhost:1521:XE", | ||
+ | "username", "password"); | ||
+ | dba.open(); | ||
+ | | ||
+ | DBStorage dbsContract = new DBStorage(); | ||
+ | dbsContract.setWritebackTable("CONTRACT"); | ||
+ | dbsContract.setDBAccess(dba); | ||
+ | dbsContract.open(); | ||
+ | | ||
+ | DBStorage dbsActivity = new DBStorage(); | ||
+ | dbsActivity.setWritebackTable("ACTIVITY"); | ||
+ | dbsActivity.setDBAccess(dba); | ||
+ | dbsActivity.open(); | ||
+ | | ||
+ | StorageDataBook sdbContract = new StorageDataBook(dbsContract); | ||
+ | sdbContract.open(); | ||
+ | sdbContract.fetchAll(); | ||
+ | sdbContract.setSelectedRow(0); | ||
+ | | ||
+ | StorageDataBook sdbActivity = new StorageDataBook(sdbContract, dbsActivity); | ||
+ | sdbActivity.setMasterReference(new ReferenceDefinition(new String[] {"CONT_ID"}, | ||
+ | sdbContract, | ||
+ | new String[] {"ID"})); | ||
+ | sdbActivity.open(); | ||
+ | | ||
+ | setLayout(new UIBorderLayout()); | ||
+ | | ||
+ | NavigationTable nav = new NavigationTable(); | ||
+ | nav.setDataBook(sdbActivity); | ||
+ | | ||
+ | add(nav); | ||
+ | | ||
+ | new SimpleTestLauncher(this); | ||
+ | } | ||
+ | |||
+ | } // TestActivity | ||
+ | </file> | ||
+ | |||
+ | The result: | ||
+ | |||
+ | {{:jvx:testapp.png?nolink|}} | ||
+ | |||
+ | The database model (Oracle): | ||
+ | |||
+ | {{:jvx:contract_activity.png?nolink|}} | ||
+ | |||
+ | The database objects (Oracle): | ||
+ | |||
+ | <file sql create.sql> | ||
+ | CREATE TABLE CONTRACT | ||
+ | ( | ||
+ | id NUMBER(16) NOT NULL, | ||
+ | title VARCHAR2(1000), | ||
+ | state NUMBER(1) | ||
+ | ) | ||
+ | ; | ||
+ | comment ON COLUMN CONTRACT.id | ||
+ | IS 'PK'; | ||
+ | comment ON COLUMN CONTRACT.title | ||
+ | IS 'Contract title'; | ||
+ | comment ON COLUMN CONTRACT.state | ||
+ | IS 'Contract state'; | ||
+ | ALTER TABLE CONTRACT | ||
+ | ADD constraint CONT_PK PRIMARY KEY (ID); | ||
+ | |||
+ | CREATE TABLE ACTIVITY | ||
+ | ( | ||
+ | id NUMBER(16) NOT NULL, | ||
+ | cont_id NUMBER(16), | ||
+ | name VARCHAR2(1000), | ||
+ | cost NUMBER(10,2), | ||
+ | valid_from DATE, | ||
+ | valid_to DATE, | ||
+ | responsible_user_id NUMBER(16), | ||
+ | validated CHAR(1) DEFAULT 'Y' | ||
+ | ) | ||
+ | ; | ||
+ | comment ON COLUMN ACTIVITY.id | ||
+ | IS 'PK'; | ||
+ | comment ON COLUMN ACTIVITY.cont_id | ||
+ | IS 'Contract FK'; | ||
+ | comment ON COLUMN ACTIVITY.name | ||
+ | IS 'Activity name'; | ||
+ | comment ON COLUMN ACTIVITY.cost | ||
+ | IS 'Activity costs'; | ||
+ | comment ON COLUMN ACTIVITY.valid_from | ||
+ | IS 'Valid from'; | ||
+ | comment ON COLUMN ACTIVITY.valid_to | ||
+ | IS 'Valid to'; | ||
+ | comment ON COLUMN ACTIVITY.responsible_user_id | ||
+ | IS 'Responsible User FK'; | ||
+ | comment ON COLUMN ACTIVITY.validated | ||
+ | IS 'Whether the activity is valid'; | ||
+ | ALTER TABLE ACTIVITY | ||
+ | ADD constraint ACTI_PK PRIMARY KEY (ID); | ||
+ | ALTER TABLE ACTIVITY | ||
+ | ADD constraint ACTI_CONT_FK FOREIGN KEY (CONT_ID) | ||
+ | REFERENCES CONTRACT (ID); | ||
+ | ALTER TABLE ACTIVITY | ||
+ | ADD constraint ACTI_RESP_USER_FK FOREIGN KEY (RESPONSIBLE_USER_ID) | ||
+ | REFERENCES USERS (ID); | ||
+ | |||
+ | |||
+ | CREATE OR REPLACE TRIGGER TR_ACTIVITY_BR_IU | ||
+ | before INSERT OR UPDATE ON activity | ||
+ | FOR each row | ||
+ | begin | ||
+ | |||
+ | IF (:new.validated = 'N') then | ||
+ | | ||
+ | IF (:new.valid_from IS NULL) then | ||
+ | raise_application_error(-20000, 'Valid from can''t be null!'); | ||
+ | end IF; | ||
+ | | ||
+ | end IF; | ||
+ | | ||
+ | end TR_ACTIVITY_BR_IU; | ||
+ | / | ||
+ | |||
+ | CREATE OR REPLACE TRIGGER TR_CONTRACT_BR_IU | ||
+ | before INSERT OR UPDATE ON contract | ||
+ | FOR each row | ||
+ | begin | ||
+ | |||
+ | IF (:new.state = 4) then | ||
+ | | ||
+ | UPDATE activity | ||
+ | SET validated = 'N' | ||
+ | WHERE cont_id = id | ||
+ | AND validated = 'Y'; | ||
+ | |||
+ | end IF; | ||
+ | | ||
+ | end TR_CONTRACT_BR_IU; | ||
+ | / | ||
+ | |||
+ | |||
+ | ALTER TABLE CONTRACT disable ALL triggers; | ||
+ | ALTER TABLE ACTIVITY disable ALL triggers; | ||
+ | ALTER TABLE ACTIVITY disable constraint ACTI_CONT_FK; | ||
+ | ALTER TABLE ACTIVITY disable constraint ACTI_RESP_USER_FK; | ||
+ | INSERT INTO CONTRACT (id, title, state) | ||
+ | VALUES (1, 'Air', 1); | ||
+ | commit; | ||
+ | INSERT INTO ACTIVITY (id, cont_id, name, cost, valid_from, valid_to, responsible_user_id, validated) | ||
+ | VALUES (1, 1, 'Part 1', .2, NULL, NULL, NULL, 'Y'); | ||
+ | commit; | ||
+ | ALTER TABLE ACTIVITY enable constraint ACTI_CONT_FK; | ||
+ | ALTER TABLE ACTIVITY enable constraint ACTI_RESP_USER_FK; | ||
+ | ALTER TABLE CONTRACT enable ALL triggers; | ||
+ | ALTER TABLE ACTIVITY enable ALL triggers; | ||
+ | </file> | ||
+ | |||
+ | ==== Authentication/User logging ==== | ||
+ | |||
+ | Some applications need detailed information about successful or failed user logins. If you have such requirement, you could create a custom security manager to solve the problem. The security manager will be used for user authentication, so it's the right place to start. | ||
+ | |||
+ | We use the database security manager as base class: | ||
+ | |||
+ | <file java LoggingSecurityManager.java> | ||
+ | package com.sibvisions.apps.vaadin.web; | ||
+ | |||
+ | public class LoggingSecurityManager extends DBSecurityManager | ||
+ | { | ||
+ | private PreparedStatement psLockedUsers; | ||
+ | |||
+ | @Override | ||
+ | protected boolean isPasswordValid(ISession pSession, String pPassword) throws Exception | ||
+ | { | ||
+ | // additional check: locked user | ||
+ | |||
+ | ResultSet resultSet = null; | ||
+ | |||
+ | try | ||
+ | { | ||
+ | psLockedUsers.clearParameters(); | ||
+ | psLockedUsers.setString(1, pSession.getUserName()); | ||
+ | psLockedUsers.execute(); | ||
+ | |||
+ | resultSet = psLockedUsers.getResultSet(); | ||
+ | |||
+ | if (resultSet.next()) | ||
+ | { | ||
+ | throw new SilentAbortException(); | ||
+ | } | ||
+ | } | ||
+ | finally | ||
+ | { | ||
+ | CommonUtil.close(resultSet); | ||
+ | } | ||
+ | |||
+ | return super.isPasswordValid(pSession, pPassword); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public synchronized void validateAuthentication(ISession pSession) throws Exception | ||
+ | { | ||
+ | String result = null; | ||
+ | |||
+ | Throwable error = null; | ||
+ | |||
+ | try | ||
+ | { | ||
+ | super.validateAuthentication(pSession); | ||
+ | |||
+ | result = Constants.RESULT_OK; | ||
+ | } | ||
+ | catch (SilentAbortException sae) | ||
+ | { | ||
+ | result = Constants.RESULT_IGNORE; | ||
+ | error = sae; | ||
+ | |||
+ | throw sae; | ||
+ | } | ||
+ | catch (SecurityException se) | ||
+ | { | ||
+ | result = Constants.RESULT_DENIED; | ||
+ | error = se; | ||
+ | |||
+ | throw se; | ||
+ | } | ||
+ | catch (Exception ex) | ||
+ | { | ||
+ | result = Constants.RESULT_ERROR; | ||
+ | error = ex; | ||
+ | |||
+ | error(ex); | ||
+ | |||
+ | throw ex; | ||
+ | } | ||
+ | finally | ||
+ | { | ||
+ | try | ||
+ | { | ||
+ | pSession.callAction("dbLog", Constants.TYPE_LOGIN, error, result); | ||
+ | } | ||
+ | catch (Throwable thr) | ||
+ | { | ||
+ | error(thr); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public synchronized void logout(ISession pSession) | ||
+ | { | ||
+ | String result = null; | ||
+ | Throwable error = null; | ||
+ | |||
+ | try | ||
+ | { | ||
+ | super.logout(pSession); | ||
+ | |||
+ | if (Boolean.parseBoolean((String)pSession.getProperty("userlogout"))) | ||
+ | { | ||
+ | result = Constants.RESULT_OK; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | result = Constants.RESULT_EXPIRED; | ||
+ | } | ||
+ | } | ||
+ | catch (Exception ex) | ||
+ | { | ||
+ | result = Constants.RESULT_ERROR; | ||
+ | error = ex; | ||
+ | |||
+ | error(ex); | ||
+ | } | ||
+ | finally | ||
+ | { | ||
+ | try | ||
+ | { | ||
+ | pSession.callAction("dbLog", Constants.TYPE_LOGOUT, error, result); | ||
+ | } | ||
+ | catch (Throwable eLog) | ||
+ | { | ||
+ | error(eLog); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | protected void initStatements(Connection pConnection) throws Exception | ||
+ | { | ||
+ | super.initStatements(pConnection); | ||
+ | |||
+ | psLockedUsers = prepareStatement(pConnection, "select * from LOCKS where USERNAME = ?"); | ||
+ | } | ||
+ | |||
+ | } // LoggingSecurityManager | ||
+ | </file> | ||
+ | |||
+ | The clue is that the log method **dbLog** was implemented in the session LCO of our application because we re-use the business logic in our security manager. Here's the missing method: | ||
+ | |||
+ | <file java> | ||
+ | public void dbLog(int pAction, Throwable pError, String pStatus) | ||
+ | { | ||
+ | ILogger log = LoggerFactory.getInstance(getClass()); | ||
+ | |||
+ | log.info("Log action ", pAction, pError, pStatus); | ||
+ | | ||
+ | try | ||
+ | { | ||
+ | getDBAccess().executeProcedure("log", | ||
+ | pAction, | ||
+ | SessionContext.getCurrentSession().getUserName(), | ||
+ | new Timestamp(System.currentTimeMillis())); | ||
+ | } | ||
+ | catch (Exception ex) | ||
+ | { | ||
+ | log.error(ex); | ||
+ | } | ||
+ | } | ||
</file> | </file> |