Chapter 5
A Larger Example

In this chapter we will create the ”My Contacts” Scout application. This small1 application covers additional aspects of the Eclipse Scout framework. The presented demo application borrows heavily from a Scout tutorial published 2012 in the German Java Magazin2 for the Scout release 3.8 (Juno). Compared to the 2012 tutorial, the version presented in this chapter has been slightly polished and updated to the Scout release 3.9 (Kepler).

Specifically, we will build an outline based Scout application featuring a navigation tree and pages to present information in tabular form. In addition, the application also shows how to work with forms to enter and/or update data, menus and context menus. On the server side, we show how to work with databases, how to use logging in Scout applications and how to include standard Java libraries available in JAR files.

The chapter is organized as follows. In the first section, the finished demo application is explained from the user perspective. The remaining sections focus on the individual steps to implement the ”My Contacts” application. To follow the description of the implementation the reader is assumed to be familiar with the ”Hello World” tutorial and the Scout SDK as described in Chapter 4.

5.1 The ”My Contacts” Application

The ”My Contacts” application is a client server application to manage personal contacts. Persistence of entered data is achieved through a database backend.

As social networking services3 such as Facebook, LinkedIn or Xing are widely used, the application also provides an example integration with the LinkedIn4 platform. The implemented integration allows to download the personal contacts into the local database. In the local database it is possible to mix persons entered manually with contact data downloaded from LinkedIn.


PIC

Figure 5.1: The SWT client of the ”My Contacts” application.


After starting the Scout server application a client application may be started that then connects to the server. In Figure 5.1 the SWT desktop client is shown. In the background, the main application window is visible showing a navigation tree on the left hand side. On the right side, a table holds the elements corresponding to the selected tree node. Using an edit context menu on the selected table row, a form to edit the relevant data may be opened as shown in the example screen shot for ’Alice’. Clicking on the link ’Show Map ...’ in the person form opens the person’s location information in a map form using the data provided by Google Maps Image API5 .


PIC

Figure 5.2: To refresh/generate an access token for reading LinkedIn contacts data select the menu shown in the screen shot.



PIC   PIC

Figure 5.3: To refresh/create the access token click on the provided ’Open Auth URL’ link first (left). Then, the security code field is enabled and the user can fill in the security code provided on the LinkedIn web page.


Before any LinkedIn data can be accessed from the ”My Contacts” application an access token needs to be retrieved from LinkedIn. To obtain such a token use the Refresh LinkedIn Token ... menu as shown in Figure 5.2. This opens the refresh token form shown on the left side of Figure 5.3.


PIC   PIC

Figure 5.4: The LinkedIn granting dialog steps shown in a web browser. In the first step (right) confirm the access request, then the security code to create an access token is provided in the second step (left).


Clicking on the ’Open Auth URL’ link then opens the granting page provided by LinkedIn shown on the left hand side of Figure 5.4. After logging into your LinkedIn account6 you can specify the desired access duration and confirm the ”OAuth test for Scribe” access7 to your LinkedIn data. If the authorization is successful, a security code as shown on the right side of Figure 5.4 is presented by LinkedIn. This code needs then to be entered into the Security Code field as shown on the right side of Figure 5.3. Then, click the OK button to refresh the access token.


PIC  

Figure 5.5: Executing the ’Update LinkedIn Contacts for the first time imports the users LinkedIn contacts into the ”My Contacts” application.


To import/update your LinkedIn contacts into your ”My Contacts” application select the Update LinkedIn Contacts menu as shown in Figure 5.5. Once you have downloaded or entered a number of persons in your ”My Contacts” application, try to get yourself familiar with the application’s person table. This is one of the very powerful Scout widgets. Columns may be filtered, moved, hidden or sorted (including multi level sort) using the table header context menus Organize Columns... menu and Column Filter... menu.

Editing and viewing of person data is available by the Edit Person... context menu on a selected row. To manually add a person use the New Person... context menu available on the table header or in the white area outside the displayed columns.


PIC

Figure 5.6: After importing the contacts from LinkedIn the data is shown in the person page. The filter applied on the headline column is indicated by the filter icon. In the front, the filter form shows the filter criteria ’Eclipse’.



PIC

Figure 5.7: The ”My Contacts” application running in the browser as web application. The Excel sheet shown in the front is exported from the person page using the ’Tools/Export to Excel’ menu.



PIC   PIC  

Figure 5.8: The ”My Contacts” application running on an iPhone device. On the left hand side, the person page is shown. The person form is shown on the right.


In Figure 5.7 the ”My Contacts” application is running in a web browser. In this example, the Tools—Export to Excel menu is used to export the selected row into an Excel sheet. Finally, the ”My Contacts” application is also running on iPhone and Android mobile devices out of the box. Two example screens are provided in Figure 5.8.

Once you no longer feel confident about having the ”My Contacts” application accessing you data you can revoke this access permission in the LinkedIn menu ”Privacy and Settings”. In the lower part of the settings page switch to the tab ”Groups, Companies and Applications” and click on the link ”View your applications”. There, you will find again the partner name ”OAuth test for Scribe”. To revoke the access, select the associated checkbox and click the ”Remove” button. The next time you try to refresh your LinkedIn data from the ”My Contacts” application will result in an Error message. Before you can again access data from your LinkedIn account you just need to refresh the access token as described above.

To run the ”My Contacts” Scout application without implementing it first, you may take advantage of the fact that the application is hosted in the same Github repository as this book. If you are familiar with Github8 , fork the Scout Book repository9 and start from there. Alternatively, you can follow the description provided in the Scout wiki10 to download, install and run the ”My Contacts” application.

5.2 Setting up the new Scout project


PIC   PIC

Figure 5.9: Start with creating a new Scout project.


The initial code for the ”My Contacts” application is generated using the New Scout Project wizard as described in Section 4.5.1. For the Project Name field use the name org.eclipsescout.contacts as shown on the left side of Figure 5.9 and click on the Next button. In the second wizard step select the application template Outline Tree and Table Form as shown on the right side of Figure 5.9.


PIC   PIC

Figure 5.10: The setting of the Technology section in the Scout Object Properties of the ”My Contacts” application.


After the Scout SDK has created the initial application code select the top-level org.eclipsescout.contacts node in the Scout Explorer. In the technology section of the corresponding Scout Object Properties select the Derby database driver, the Docx4j support and the Rayo look and feel as shown on the right side of Figure 5.10. In case you have not yet used the Scout Docx4j support or the Rayo look and feel components, the Scout SDK will need to download these packages from the Eclipse Marketplace11 first.


Listing 5.1: The ExportToExcelMenu class added by the Docx4j support to the application’s tools menu.
 
   @Order(20.0) 
   public class ExportToExcelMenu extends AbstractExtensibleMenu { 
 
    @Override 
    protected String getConfiguredText() { 
      return TEXTS.get("ExportToExcelMenu"); 
    } 
 
    @Override 
    protected void execAction() throws ProcessingException { 
      if (getOutline() != null && getOutline().getActivePage() != null) { 
       ScoutXlsxSpreadsheetAdapter s = new ScoutXlsxSpreadsheetAdapter(); 
       File xlsx = s.exportPage(null, 0, 0, getOutline().getActivePage()); 
       SERVICES.getService(IShellService.class).shellOpen(xlsx.getAbsolutePath()); 
      } 
    } 
   } 
 }

Adding the Docx4j support will also add the Export to Excel menu under tools menu on the application’s desktop. As shown in Listing 5.1, the ScoutXlsxSpreadsheetAdapter first creates an excel file based on the currently active page in the execAction method. Then the file is opened on the client using the shellOpen method.


PIC   PIC

Figure 5.11: Configure the application name in the title field of the Desktop’s properties.


To set the applications name select the Desktop node in the Scout Explorer to access it’s Scout Object Properties as shown in Figure 5.11. In the Title field enter the string ”My Contacts” and create a new translated text entry.


Listing 5.2: The execOpened method of desktop class of the ”My Contacts” application. The application’s organisation into a tree and a table form is defined here.
 
 @Override 
 protected void execOpened() throws ProcessingException { 
   //a mobile home form (in client.mobile plugin) is used instead. 
   if (!UserAgentUtility.isDesktopDevice()) { 
    return; 
   } 
 
   // outline tree 
   DefaultOutlineTreeForm treeForm = new DefaultOutlineTreeForm(); 
   treeForm.setIconId(Icons.EclipseScout); 
   treeForm.startView(); 
 
   //outline table 
   DefaultOutlineTableForm tableForm = new DefaultOutlineTableForm(); 
   tableForm.setIconId(Icons.EclipseScout); 
   tableForm.startView(); 
 
   if (getAvailableOutlines().length > 0) { 
    setOutline(getAvailableOutlines()[0]); 
   } 
 }

As shown in Listing 5.2 the application’s organisation into a tree and a table form is explicitly defined in method execOpened. Click on the Exec Opened link in the Scout Object Properties to access the Java code of this method. First, using the UserAgentUtility class, the method checks if the client is working with a desktop or a web client. If not, the method returns and the tree and table setup is not used. Instead, the MobileHomeForm defined in plugin org.eclipsescout.contacts.client.mobile is used. In case the user is working with a web client or a desktop client, default tree and table forms are created and started. Finally, the currently active outline is set to the StandardOutline, as this is the only outline defined in this application.

5.3 Adding the Person Page


PIC PIC PIC

Figure 5.12: Add the person table page below the standard outline.


The first UI component we add to the application is the person page. For the desktop clients, this page is represented as a table that will list all available persons in the database of the ”My Contacts” application. To start the New Page wizard use the New Page... context menu on the Child Pages folder as shown in Figure 5.12. On the first wizard step select the template AbstractPageWithTable and click the Next button. On the second wizard step, provide the page name ”Person” to create a new translated text entry at the same time. Make sure the other fields are filled in as shown in Figure 5.12 and click the Finish button to close the wizard.


Listing 5.3: The execCreateChildPages method of the standard outline. At the current implementation step the company table page is not (yet) added.
 
 @Override 
 protected void execCreateChildPages(Collection<IPage> pageList) throws ProcessingException { 
   PersonTablePage personTablePage = new PersonTablePage(); 
   pageList.add(personTablePage); 
   pageList.add(new CompanyTablePage()); 
 } 
}

Listing 5.3 shows the created method execCreateChildPages that links the newly created person page to the standard outline. Note that your code will only look the same once you have added the company page in a later step of the implementation of this application.


PIC PIC PIC

Figure 5.13: Add columns to the person page.


Now, drill down to the Columns folder under the PersonTablePage node as shown in Figure 5.14. Here we can add the desired table columns to the person page. Start with the column that will hold the person id. For this, start the column wizard as shown on the left side of Figure 5.14 and select the string column template. In the second wizard step, enter ”PersonId” into the Class Name field,select the radio button Create one more column and click on the Finish button. This will restart the column wizard to enter the next columns. Create the remaining string columns with the following names.


PIC   PIC

Figure 5.14: Configure the PersonId column. Check property Primary Key under the section Advanced Properties (right).



Listing 5.4: The person id and the first name columns of the PersonTablePage class.
 
   @Order(10.0) 
   public class PersonIdColumn extends AbstractStringColumn { 
 
    @Override 
    protected boolean getConfiguredDisplayable() { 
      return false; 
    } 
 
    @Override 
    protected boolean getConfiguredPrimaryKey() { 
      return true; 
    } 
   } 
 
   @Order(20.0) 
   public class FirstNameColumn extends AbstractStringColumn { 
 
    @Override 
    protected String getConfiguredHeaderText() { 
      return TEXTS.get("FirstName"); 
    } 
   }

Once you have created all these columns we will mark the person id column as the primary key column for the person page. In the Scout Explorer select the PersonIdColumn node to open the corresponding Scout Object Properties. In this form, deselect the Displayable property to always hide this technical column from the end user. In the properties Advanced Properties section check the Primary Key property. The resulting Java code for the primary key column and the first name column is provided in Listing 5.5.


PIC   PIC

Figure 5.15: Add the CompanyId variable to the person page.



Listing 5.5: The company id variable of the PersonTablePage class.
 
public class PersonTablePage extends AbstractPageWithTable<PersonTablePage.Table> { 
 
 private String m_companyId; 
 @FormData 
 public String getCompanyId() { 
   return m_companyId; 
 } 
 
 @FormData 
 public void setCompanyId(String companyId) { 
   m_companyId = companyId; 
 } 
}

As we will later add and link the person page with a company page, we add a company id variable to the person page as shown in Figure 5.15. For the Java representation of such variables the standard bean pattern is used as shown in Listing 5.5.

5.4 Adding the Company Page


PIC   PIC

Figure 5.16: Add the person page below the the company page.



Listing 5.6: Linking the PersonTablePage with the parent CompanyTablePage.
 
 @Override 
 protected IPage execCreateChildPage(ITableRow row) throws ProcessingException { 
   PersonTablePage childPage = new PersonTablePage(); 
   childPage.setCompanyId(getTable().getCompanyIdColumn().getValue(row)); 
   return childPage; 
 }

After adding the person page, we add a company page to the standard outline. To add the company page we use the same wizards as described in the previous section for the person page. For the Name field we enter the new translated text ”Company” and the columns to add are the following.

As in the case of the person table page, the company id column is used as a primary key column. The Displayable property needs to be set to false and the Primary Key property to true. Now we can link the person page to the company page using the Add Existing Page... context menu as shown on the left side of Figure 5.16. In the Link Page wizard, the person page can then be selected according to the right side of Figure 5.16. In the Java code generated by the Scout SDK the setting of the company id attribute is automatically inserted in method execCreateChildPages. Please note that this convenience added by the Scout SDK wizard only works if the child page defines variables with a naming that matches the defined primary key columns of the parent table.

5.5 Installing the Database


PIC   PIC

Figure 5.17: Add the service to access the Derby database under folder SQL Services.


To access a database we first need to install a database service. For the ”My Contacts” application, this is done using the New SQL Service wizard on the Scout server under the SQL Services folder as shown in Figure 5.17. In the first wizard step shown on the right side of the figure, use ”DerbySqlService” for the Class Name field. From the drop-down list in the Super Class field choose the AbstractDerbySqlService and click on the Finish button.


Listing 5.7: Setting up the database parameters in the Scout server’s config.ini file.
 
### DataSource 
org.eclipsescout.contacts.server.services.common.sql.DerbySqlService#directJdbcConnection=true 
org.eclipsescout.contacts.server.services.common.sql.DerbySqlService#jdbcDriverName=org.apache.derby.jdbc.EmbeddedDriver 
org.eclipsescout.contacts.server.services.common.sql.DerbySqlService#jdbcMappingName=jdbc:derby:${workspace_loc}/../DB_CONTACT;create=true;territory=en_US 
org.eclipsescout.contacts.server.services.common.sql.DerbySqlService#username=contact_user 
org.eclipsescout.contacts.server.services.common.sql.DerbySqlService#password=secr3t

To setup the database connection the necessary parameters need to be added to the server’s config.ini file as shown in Listing 5.7. Comparing the parameter names in this config file with the package name of the created DerbySqlService service class reveals an interesting framework feature. All Scout services can be parameterized using the config.ini file with the pattern <package>.<class name>#<parameter>=<value>. The Scout runtime then sets the service parameters using matching setter methods such as setPassword for the password parameter.

According to the parameter jdbcMappingName=jdbc:derby:${workspace_loc}/... a new Derby database will be created in the same parent directory as the ”My Contacts” workspace directory if no database named DB_CONTACT is found there. This setup is handy for development purposes but you may want to set the database parameter to create=false in the config.ini of the production product file.


PIC   PIC

Figure 5.18: Add a database installation service.



PIC   PIC

Figure 5.19: Add the service operation to create the DB schema.



Listing 5.8: The installStorage service operation to create the database schema for the ”My Contacts” application. New tables are created if they are not found by getExistingTables in the existing schema.
 
public class DBInstallService extends AbstractService implements IDBInstallService { 
 
 private boolean m_doSetup = true; 
 
 public void setDoSetup(boolean doSetup) { 
   m_doSetup = doSetup; 
 } 
 
 @Override 
 public void installStorage() throws ProcessingException { 
   boolean addInitialData = true; 
   if (m_doSetup) { 
    Set<String> tables = getExistingTables(); 
    createCompanyTable(tables, addInitialData); 
    createPersonTable(tables, addInitialData); 
    createUsersParamTable(tables, addInitialData); 
   } 
 } 
 
 private Set<String> getExistingTables() throws ProcessingException { 
   Object[][] existingTables = SQL.select("SELECT tablename FROM sys.systables"); 
   HashSet<String> result = new HashSet<String>(existingTables.length); 
   for (Object[] row : existingTables) { 
    String table = (row[0] + "").toUpperCase(); 
    result.add(table); 
   } 
   return result; 
 } 
}

5.5.1 Setting up the Database Schema

Having a working database service and a new (empty) Derby database now allows to create the necessary database schema and populate it with some initial data. For this we add a new DBInstallService service as shown in Figure 5.18. To this install service we then add the installStorage operation according to Figure 5.19. The implementation of this method is provided in Listing 5.8. The method first checks if a setup is required. For this, the member variable m_doSetup is used that might also be set by the setDoSetup setter method via the server’s config.ini file.


Listing 5.9: Setting up the COMPANY table of the ”My Contacts” application.
 
 private void createCompanyTable(Set<String> tables, boolean addInitialData) throws ProcessingException { 
   if (!tables.contains("COMPANY")) { 
    SQL.insert("CREATE TABLE COMPANY (" 
       + " company_id VARCHAR(64) NOT NULL CONSTRAINT COMPANY_PK PRIMARY KEY, " 
       + " name VARCHAR(64), " 
       + " location VARCHAR(64), " 
       + " url VARCHAR(64))"); 
 
    if (addInitialData) { 
      SQL.insert("INSERT INTO COMPANY (company_id, name, location, url) " 
         + "VALUES (:company_id, Alice’’s Adventures in Wonderland’, London, England’, http://en.wikipedia.org/wiki/Alice%27s_Adventures_in_Wonderland’)", 
         new NVPair("company_id", UUID.randomUUID().toString())); 
 
      SQL.insert("INSERT INTO COMPANY (company_id, name, location, url) " 
         + "VALUES (:company_id, BSI Business Systems Integration AG’, Daettwil, Baden, Switzerland’, http://www.bsiag.com’)", 
         new NVPair("company_id", UUID.randomUUID().toString())); 
    } 
   } 
 }


Listing 5.10: Setting up the PERSON table of the ”My Contacts” application.
 
 private void createPersonTable(Set<String> tables, boolean addInitialData) throws ProcessingException { 
   if (!tables.contains("PERSON")) { 
    SQL.insert("CREATE TABLE PERSON (" 
       + " person_id VARCHAR(64) NOT NULL CONSTRAINT PERSON_PK PRIMARY KEY, " 
       + " company_id VARCHAR(64), " 
       + " first_name VARCHAR(64), " 
       + " last_name VARCHAR(64), " 
       + " headline VARCHAR(512), " 
       + " location VARCHAR(512), " 
       + " date_of_birth DATE, " 
       + " picture_url VARCHAR(512), " 
       + " CONSTRAINT COMPANY_FK FOREIGN KEY (company_id) REFERENCES COMPANY (company_id))"); 
 
    if (addInitialData) { 
      SQL.insert("INSERT INTO PERSON (person_id, first_name, headline, location, company_id, picture_url, date_of_birth)" 
         + "VALUES (:person_id, Alice’, The curious girl’, Daresbury, Cheshire, England’, " 
         + "(SELECT company_id FROM COMPANY WHERE name = Alice’’s Adventures in Wonderland’), http://www.uergsel.de/uploads/Alice.png’, :dob)", 
         new NVPair("person_id", UUID.randomUUID().toString()), 
         new NVPair("dob", DateUtility.parse("26.11.1865", "dd.mm.yyyy"))); 
    } 
   } 
 }


Listing 5.11: Setting up the USERS_PARAM table of the ”My Contacts” application.
 
 private void createUsersParamTable(Set<String> tables, boolean addInitialData) throws ProcessingException { 
   if (!tables.contains("USERS_PARAM")) { 
    SQL.insert(" CREATE TABLE USERS_PARAM (" 
       + " username VARCHAR(32) NOT NULL, " 
       + " param VARCHAR(32) NOT NULL, " 
       + " value VARCHAR(512), " 
       + " CONSTRAINT PARAM_PK PRIMARY KEY (username, param)" 
       + ")"); 
 
    if (addInitialData) { 
      // nop 
    } 
   } 
 }

Setting up the schema to contain the individual tables for the ”My Contacts” application is implemented in a separate method per table. The table definition for the company table is provided in Listing 5.9. In this method two Scout aspects are of interest.

The first Scout feature used is the absence of a COMMIT statement after the two INSERT INTO statements. This is possible, as all Scout service calls run in a transaction context that is transparent to the Scout developer. If a service method exits without errors, the enclosing transaction is committed. And if any runtime exception occurs in a service call the Scout runtime framework performs a rollback.

The second feature is the parameter binding used in the INSERT INTO statements. When SQL statements are executed using any of the static SQL methods, an internal statement processor replaces all bind variables found in the provided statement string. In Scout, SQL bind variables need to use the pattern :<variable name>. The values for the bind variables can then be provided in the form of additional arguments. In the concrete example of Listing 5.9, the content for the bind variable :company_id is provided as a NVPair object. NVPairs are the simplest possible form to represent bind variables. The first constructor argument is the variable name of the bind variable, in this case company_id. The actual content of the bind variable is provided in the form of an object. Here, the Java runtime class UUID.randomUUID().toString() is used to create a new company key.

Setting up the person table and the user parameter table is defined according to Listing 5.10 and Listing 5.11. To create a data object for the persons day of birth, the Scout utility class DateUtility is used.


Listing 5.12: Scheduling the database installation in the start method of the ServerApplication class during the server application startup.
 
public class ServerApplication implements IApplication { 
 private static IScoutLogger logger = ScoutLogManager.getLogger(ServerApplication.class); 
 
 @Override 
 public Object start(IApplicationContext context) throws Exception { 
   ServerSession serverSession = SERVICES.getService(IServerSessionRegistryService.class).newServerSession(ServerSession.class, Activator.getDefault().getBackendSubject()); 
 
   ServerJob installJob = new ServerJob("Install the contacts DB schema", serverSession) { 
    @Override 
    protected IStatus runTransaction(IProgressMonitor monitor) { 
      try { 
       SERVICES.getService(IDBInstallService.class).installStorage(); 
       logger.info("Contacts DB schema successfully created"); 
      } 
      catch (Throwable t) { 
       return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error while installing the contacts DB schema", t); 
      } 
 
      return Status.OK_STATUS; 
    } 
   }; 
   installJob.schedule(); 
   installJob.join(20000); 
 
   logger.info("Contacts server application started"); 
   return EXIT_OK; 
 } 
}

The only piece missing to setup the database is calling the installStorage operation during the startup of the ”My Contacts” server application. The proper way to implement such a scenario is to schedule an installation job in the ServerApplication class of the Scout server. In the ”My Contacts” application this is implemented according to Listing 5.12. To create a server job, a new server session object needs to be obtained first. However, during the startup time of the server we do not have any logged in users yet. That is why the session is created for the backend subject representing the server application. Using this serverSession object, the installJob can be created. In it’s runTransaction method we can then call the installStorage operation to setup the ”My Contacts” database.

5.5.2 Scout Logging


Listing 5.13: The logging configuration in the Scout server’s config.ini file.
 
eclipse.consoleLog=true 
org.eclipse.scout.log=eclipse 
org.eclipse.scout.log.level=INFO

An additional Scout topic that is touched in this ServerApplication class is the Scout logging. As shown in Listing 5.12, a static logger object is created using Scout’s ScoutLogManager class. Events can then be logged with the logger.info method where info refers to the log level attached to this message. Similar to the database setup described above, the logging setup can be defined in config.ini file. The default setup defined for the development product is provided in Listing 5.13. Further information regarding logging in Eclipse Scout is available in the Scout wiki12 .

5.6 Fetching Data from the Database


PIC   PIC

Figure 5.20: Add the service operation to fetch the data for the person table.


In the section before, we have implemented the access to the Derby database for the Scout server. And during the server startup, the application’s database schema can now be created and populated with some initial data.

Here, this schema and the implemented database access is used to fetch the data for the client application’s person page and the company page. The best place to implement these data provider methods is in the server’s StandardOutlineService class. This class has been created by the Scout SDK during the initial project creation step. It is meant to hold the operations that fetch the data for populating the elements visible in the outline tree and outline pages.

For the ”My Contacts” application, we first create the getPersonTableData operation as shown in Figure 5.20. In the wizard dialog enter ”getPersonTableData” into the Operation Name field. For the return type we simply use a two dimensional object array. As the person page is also displayed under the company page, we need to have a way to only return the persons working for a specific company. To allow for this use case, we add the parameter ”companyId” as the first argument to the getPersonTableData operation before we close the wizard with Finish button.

The next operation we need is the getCompanyTableData method. You may use the same creation steps as described above for the person table page data. But for fetching company table data we do not need an additional argument. The company page in the ”My Contacts” application will always show all available companies.


Listing 5.14: Fetching the table data for the person and the company page of the ”My Contacts” application. The list of persons is restricted to employees of a specific company if a company id parameter is provided.
 
public class StandardOutlineService extends AbstractService implements IStandardOutlineService { 
 
 @Override 
 public Object[][] getPersonTableData(String companyId) throws ProcessingException { 
   String stmt = "SELECT person_id, first_name, last_name, headline FROM PERSON"; 
 
   if (StringUtility.isNullOrEmpty(companyId)) { 
    return SQL.select(stmt); 
   } 
 
   return SQL.select(stmt + " WHERE company_id = :companyId", 
      new NVPair("companyId", companyId)); 
 } 
 
 @Override 
 public Object[][] getCompanyTableData() throws ProcessingException { 
   return SQL.select("SELECT company_id, name, location FROM COMPANY"); 
 } 
}

For the actual implementation of the two data fetching operations the code provided in Listing 5.14 is used. The implementation is straight forward and almost trivial. However, we can use the getPersonTableData example to introduce one of the many Scout utility classes.


PIC

Figure 5.21: Accessing the Scout utility classes with the pattern org.eclipse.scout.*Utility.


The class StringUtility is one of the many utility classes provided by the Scout framework. Here, it is used for the typical null or empty check. To get a quick overview, hit the <Ctrl><Shift><T> key combination. In the type dialog that appears enter org.eclipse.scout.*Utility into the pattern field. This will display the complete list of the Scout utility classes as shown in Figure 5.21.

5.7 Creating the Person Form


PIC   PIC

Figure 5.22: Add the person form.


In this section we will create the person form that is used to display and edit the persons stored in the ”My Contacts” application. To add the person form use the Scout SDK New Form wizard as shown in Figure 5.22. In the first wizard step you just need to enter ”Person” as a new translated text into the Name field and add ”ui.forms” as the sub-package name in the corresponding wizard field. Then click the Finish button.

The wizard will create the necessary artefacts in the application’s client, the shared and the server plugin projects. On the client side the PersonForm class with the necessary form handlers is created, in the shared part, the PersonFormData transfer object is added. On the server side, a PersonProcessService with all necessary service operations referenced by the form handlers is implemented.


PIC   PIC

Figure 5.23: Add the PersonId variable to the person form.


To hold the persons primary key in the person form we also need to add a corresponding variable. For this, use the New Property Bean wizard as shown in Figure 5.23. In the wizard dialog, enter ”PersonId” into the Name field and pick String from the dropdown list provided in the Bean type field. Then, click the Finish button to close the wizard.

5.7.1 Creating the Form Layout with From Fields


PIC   PIC

Figure 5.24: Add the first group box field to the person form.


As a next step we create the main layout structure of the person form. According to the screenshot of the person form shown in Figure 5.1 the form is organized into an upper box including the first name, the last name and a picture field. In the ”Details” box in the lower half of the form some additional fields are found, such as the date of birth field. The bottom of the form holds a ”Show Map ...” link. We start with the general layout by adding the PersonBox according to Figure 5.24. And again on the person’s from MainBox node we add a second group box field using the label ”Detail”. The link is added with the New Form Field wizard too. In the first wizard step select the type LinkButton and use a new translated text ”Show Map ...” for the Name field

We now add the form fields listed below using the the New Form Field wizard multiple times. Most fields are of type StringField and different field types are separately indicated.


PIC   PIC

Figure 5.25: Add the picture field to the first group box of the person form.


As an example for adding the form fields, the process is illustrated in Figure 5.25 for the creation of the picture field. In the field list we need to set non default properties for the PictureUrlField field and the PictureField field. The picture URL field will hold an URL pointing to the picture of the person to be displayed. As in the person form only the picture is to be displayed but not the picture URL, we need to make this field invisible. For this, select the PictureUrlField node in the Scout Explorer and then untick the Visible field in the Scout Object Properties. Note that to access the visibility property you need to open the section Advanced Properties first.


PIC   PIC

Figure 5.26: Set the properties for the picture field of the person form.


As the configuration of the picture field is more complex than the other fields, the changed properties are shown in the screenshots provided in Figure 5.26. First, no label is shown for the picture field as shown in the unticked Label Visible field of the Scout Object Properties. Then, property Grid H is set to 5. This results in the picture to cover the vertical space of 5 form fields.


Listing 5.15: The behaviour of the person form’s PictureField is triggered by changes in field PictureUrlField.
 
    @Order(40.0) 
    public class PictureField extends AbstractImageField { 
      @Override 
      protected Class<? extends IValueField> getConfiguredMasterField() { 
       return PictureUrlField.class; 
      } 
 
      @Override 
      protected void execChangedMasterValue(Object newMasterValue) throws ProcessingException { 
       try { 
         URL url = new URL((String) newMasterValue); 
         setImage(IOUtility.getContent(url.openStream())); 
         setAutoFit(true); 
       } 
       catch (Exception e) { 
         e.printStackTrace(); 
       } 
      } 
    }

Finally, we want the picture to be refreshed whenever the content of the picture URL is changed. For this, the property Master Field is set to the PictureUrlField. The implementation of the corresponding method execChangedMasterValue is provided in Listing 5.15. As we can see, the Scout helper class IOUtility is used to read the image content from the provided URL. This content is then used to assign the image content with the image field’s method setImage. Method setAutoFit is called to adapt the picture to the dimensions available to the image field.

5.7.2 A simple Form to edit the Picture URL


PIC   PIC

Figure 5.27: Add the URL editor form.


To edit a person’s picture link, we create a simple URL editor form as shown in Figure 5.27. As we only need this form to update the URL information of a person’s picture field, we do not need any connectivity to the backend of the ”My Contacts” application. That is why almost all form and service artefacts are deselected in the second wizard step shown on the right side of Figure 5.27.


Listing 5.16: The UI structure of the PictureURLForm used to update the URL of the picture field in the person form. The rest of the class created by the Scout SDK has been omitted.
 
public class PictureURLForm extends AbstractForm { 
 @Order(10.0) 
 public class MainBox extends AbstractGroupBox { 
 
   @Order(10.0) 
   public class UrlBox extends AbstractGroupBox { 
 
    @Order(10.0) 
    public class URLField extends AbstractStringField { 
 
      @Override 
      protected String getConfiguredLabel() { 
       return TEXTS.get("URL"); 
      } 
    } 
   } 
 } 
}

As this form only holds a single URL field, we omit the description of the creation of the URL editor form’s content and provide the resulting Java code instead. In Listing 5.16 just the form’s MainBox code is shown.


PIC   PIC

Figure 5.28: Add the URL edit menu to the picture field.


This form is then started via an ”Edit URL ...” contextmenu on the image field. The creation of this contextmenu is shown in Figure 5.28. See Listing 5.17 for the actual implementation of the execAction for this contextmenu.


Listing 5.17: The edit menu implemented in class EditURLMenu of the picture field. If the URL was changed the picture URL field of the person form is set accordingly in method execAction
 
      @Order(10.0) 
      public class EditURLMenu extends AbstractExtensibleMenu { 
 
       @Override 
       protected String getConfiguredText() { 
         return TEXTS.get("EditURL"); 
       } 
 
       @Override 
       protected void execAction() throws ProcessingException { 
         PictureURLForm form = new PictureURLForm(); 
         form.startModify(); 
         form.waitFor(); 
         if (form.isFormStored()) { 
          getPictureUrlField().setValue(form.getURLField().getValue()); 
         } 
       } 
      }

Once the edit URL form is started with form.startModify() the client waits in method form.waitFor until the form is closed by the user. If the user has changed any field content (the picture URL in our case) and closed the form with the OK button, the method form.isFormStored returns true, and we can copy the new URL from the editor form field into the picture URL field of the person form. Such a change will then trigger method execChangedMasterValue of the PictureField which in turn updates the image shown in the person form.

5.7.3 Linking the Person Form to the Person Page


PIC   PIC

Figure 5.29: Add the Person edit menu to the person page.



Listing 5.18: The EditPersonMenu to edit the person selected in the person table. The person id taken from the corresponding (invisible) column is transferred to the person form before the form is started.
 
   @Order(10.0) 
   public class EditPersonMenu extends AbstractExtensibleMenu { 
 
    @Override 
    protected String getConfiguredText() { 
      return TEXTS.get("EditPerson"); 
    } 
 
    @Override 
    protected void execAction() throws ProcessingException { 
      PersonForm form = new PersonForm(); 
      form.setPersonId(getPersonIdColumn().getSelectedValue()); 
      form.startModify(); 
      form.waitFor(); 
      if (form.isFormStored()) { 
       reloadPage(); 
      } 
    } 
   }

The user of the ”My Contacts” application wants to use the person form to show/edit attributes of a specific person. To support this use case, we need to link this form with the ”My Contacts” application. As we already have person pages that show some of the person’s attribute, we can now add context menus to this list to open/edit existing persons in the person form and to create new persons as well. This is achieved by using the New Menu wizard on the Menus node of the table of the person page according to Figure 5.29. In the Name field enter the new translated text ”Edit Person ...”. The form to start is the PersonForm and the ModifyHandler is the form handler to be used to start the form. As we have defined a meaningful primary key column on the person page and a matching variable is available for the person form, the Scout SDK wizard is generating the necessary code automatically. The implementation of the execAction method provided in Listing 5.18 works out of the box and should not need any manual tuning. Now, you may also add the ”New Person ...” menu in the same way. Except that you pick the NewHander in the New Menu wizard instead of the modify handler.


PIC   PIC

Figure 5.30: Set the behaviour for the row level action on the person table.



Listing 5.19: The execRowAction method on table pages is used to trigger an action when a row is selected with a double click or <Enter>.
 
   @Override 
   protected void execRowAction(ITableRow row) throws ProcessingException { 
    getMenu(EditPersonMenu.class).execAction(); 
   }

To open the person form with a double click on a person row or by hitting the <Enter> key, you may add a corresponding execRowAction on the person page. This method can be added to the person table by clicking on the green plus icon next to the operation Exec Row Action as shown in Figure 5.30. For the implementation of this method you may reuse the functionality implemented for the context menu according to Listing 5.19.

5.7.4 Adding the Company Smartfield

At the current stage of the ”My Contacts” application, we have no option to manage the relationship between people and companies. To manage this relation, we now add a company smart field to the person form. This smart field will then hold the current assignment of the person represented in the person form.

A Scout smart field can be viewed as user friendly dropdown field on steroids that also implements search-as-you-type to pick a specific entry. In the simplest case the smart field provides access to a small and locally provided list of key value pairs. But for the intended use in the ”My Contacts” application, we will need to access a list of elements provided by the server that will be compiled dynamically at runtime.


PIC

Figure 5.31: Add a lookup call to the applications shared node.



PIC   PIC

Figure 5.32: The two wizard steps to enter the details of the company lookup call.


To create the access to this list, we start with the creation of the company lookup call. As shown in Figure 5.31 the lookup call is added on the Lookup Calls folder under the green shared node of the ”My Contacts” application. This opens the New Lookup Call wizard as shown in Figure 5.32. In the first wizard step, enter ”Company” into the Class Name field and verify that the wizard step looks the same as the screenshot shown on the left hand side of Figure 5.32. Before the wizard is closed, click on the Next button to move to the second wizard step. As shown on the right hand side of Figure 5.32 the wizard will also create a corresponding CompanyLookupService on the application’s server. We can now close this wizard with the Finish button.


Listing 5.20: The company lookup call with its getConfiguredService method in the application’s shared plugin.
 
public class CompanyLookupCall extends LookupCall{ 
 
 private static final long serialVersionUID = 1L; 
 
 @Override 
 protected Class<? extends ILookupService> getConfiguredService() { 
   return ICompanyLookupService.class; 
 } 
}


Listing 5.21: The company lookup service in the application’s server plugin. The key and the text criteria are used to search for values by key or by the provided name substring. index
 
public class CompanyLookupService extends AbstractSqlLookupService implements ICompanyLookupService { 
 
 @Override 
 protected String getConfiguredSqlSelect() { 
   return "SELECT " 
      + "company_id, " 
      + "name " 
      + "FROM COMPANY " 
      + "WHERE 1=1 " 
      + "<key> AND company_id = :key </key> " 
      + "<text> AND UPPER(name) LIKE UPPER(:text||’%’) </text> " 
      + "<all> </all> "; 
 } 
}

The CompanyLookupCall just created by the Scout SDK wizard is provided in Listing 5.20. As we can see, the only method implemented is getConfiguredService that points to the specific server service to be used. In the Scout Explorer, the new company lookup service can be found in the Lookup Services folder under the blue server node of the application. In this service, we need to implement method getConfiguredSqlSelect as shown in Listing 5.21. For Scout lookup services, specific key, text and all criteria blocks need to be provided. This criteria are included in the SELECT statement using the <key>, <text> and <all> tags as shown in the listing. The Scout runtime uses the <key>-block in cases where a specific key is already assigned to the smart field. The <text>-block is used as a query criteria to create the dynamic search-as-you-type hit list based on the (sub)string entered by the user so far. Finally, the <text>-block is used to define the hit list to be shown when the user does not enter any text into the smart field but clicks on the field’s search icon instead. The bind variable :key and :text are provided by Scout and hold the value of the assigned key or the text entered into the smart field.


PIC

Figure 5.33: Add a smart field to the person form.



PIC   PIC

Figure 5.34: Create the company smart field for the person form.


We are now ready to add the company smart field to the person form. To start the New Form Field wizard we use the context menu on the DetailBox of the person form as shown in Figure 5.34. In the first wizard step, we chose the SmartField entry as the field type and click the Next button. Then, we enter ”Company” into the Name field as shown on the right hand side of Figure 5.34. Make sure that you select the String entry in the Generic Type field as we are using string values to identify companies in the ”My Contacts” application. And in the LookupCall field, we can now select the CompanyLookupCall that we have just created before. Finally, the position of the new company smart field can be set in the Sibling field before the location field before the wizard is closed with the Finish button.


Listing 5.22: The smart field CompanyField of the person form and its wiring with the company lookup call.
 
    @Order(20.0) 
    public class CompanyField extends AbstractSmartField<String> { 
 
      @Override 
      protected String getConfiguredLabel() { 
       return TEXTS.get("Company"); 
      } 
 
      @Override 
      protected Class<? extends LookupCall> getConfiguredLookupCall() { 
       return CompanyLookupCall.class; 
 
      } 
    }

The implementation of the company smart field created by the Scout SDK wizard is provided in Listing 5.22. A look at the implementation of the CompanyField class shows its simplicity and the wiring with the company lookup service.

5.7.5 Adding the Map Form

We now want to add the ”Map” form shown in the front of Figure 5.1. The purpose of this form is to show a map corresponding to the address entered into the location field of the person form using the Google Maps Image API13 . This also implies that only addresses that can correctly be parsed by the Maps Image API will lead to a useful map image.

To create the maps form we start the New Form wizard and enter the new translated text ”Map” into the Name field of the first wizard step. Then, we click the Next button to configure the artefacts to be created by the wizard. For the map form we can use the configuration as shown on the right hand side of Figure 5.27 with the difference that we do not need the Ok button. Having deselected most artefacts, the wizard can be closed with the Finish button.

After the form creation wizard has been closed, update the label of the cancel button to ”Close” in the button’s Scout Object Properties. Then, we can add an ”Address” variable to the form by starting the New Property Bean wizard on the Variables node of the newly created map form. In the property bean wizard, enter ”Name” into the Name field and set the Bean type field to String.

As the next step, the map image field is added to the from. For this, start the New Form Field wizard directly on the form’s MainBox node. In the first form field wizard step, select ImageField as the field type and click on the Next button. Before you can close the second wizard step with the Finish button, enter ”Map” into the Class Name field. To set the properties of the new map field, select the MapField node below the main box node of the map form. In the MapField’s Scout Object Properties untick the Label Visible property and add an execInitField method by clicking on the green plus icon next to this operation. The configuration of the map field can then be completed in section Advanced Properties. Here, we set the Grid H property to 6 and update the Width in Pixel property and the Height in Pixel property to a value of 400 each.


Listing 5.23: In the execInitField method of the map form the image content is fetched from the Google Maps API.
 
    @Override 
    protected void execInitField() throws ProcessingException { 
      String address = StringUtility.nvl(getAddress(), ""); 
      String size = "" + getConfiguredHeightInPixel() + "x" + getConfiguredWidthInPixel(); 
      String url = null; 
 
      try { 
       url = "http://maps.googleapis.com/maps/api/staticmap?center=" + 
          URLEncoder.encode(address, "ISO-8859-1") + 
          "&zoom=13&size=" + size + "&maptype=roadmap&sensor=false"; 
       setImage(IOUtility.getContent((new URL(url)).openStream())); 
      } 
      catch (Exception e) { 
       setErrorStatus(new ProcessingStatus("Bad Link: " + url + "’, please check", 
          ProcessingStatus.ERROR)); 
       setImage(null); 
       e.printStackTrace(); 
      } 
    }

To add the Java code to display the map in the image field, click on the execInitField link in the Scout Object Properties of the map field. According to the implementation provided in Listing 5.23, an URL for the Maps Image API is first constructed. This URL also contains the content of the map form’s address variable and the configured dimension of the map field. The map picture returned by the Google API is then read using IOUtility.getContent and directly fed into the image fields setImage method.

The last step involving the implementation of the map form feature is its integration into the person form. As visible on the lower left part of the person form shown in Figure 5.1, a Show Map ... link is available. We now need to add such a link to the person form and add the necessary wiring for opening the newly created map form if the user clicks on this link. As a first step, the New Form Field wizard is started on the MainBox node. In the first wizard step, select the LinkButton from the available field types and click the Next button to load the second wizard step. Here, just enter the new translated text ”Show Map ...” into the Name field and close the wizard with the Finish button.


Listing 5.24: The edit menu implemented in class EditURLMenu of the picture field. If the URL was changed the picture URL field of the person form is set accordingly in method execAction
 
   @Order(50.0) 
   public class ShowMapButton extends AbstractLinkButton { 
 
    @Override 
    protected String getConfiguredLabel() { 
      return TEXTS.get("ShowMap"); 
    } 
 
    @Override 
    protected void execClickAction() throws ProcessingException { 
      MapForm mapForm = new MapForm(); 
      mapForm.setAddress(getLocationField().getValue()); 
      mapForm.startModify(); 
    } 
   } 
 }

To add the necessary wiring code to the link button double click the ShowMapButton node in the Scout Explorer and implement its execClickAction method. As shown in Listing 5.24, we only need to create a new map form in the click action, set its address variable and open the form with form.startModify.

5.8 Managing Person Data on the Server Side


Listing 5.25: The execLoad and the execStore methods of the person form’s modify handler.
 
 public class ModifyHandler extends AbstractFormHandler { 
 
   @Override 
   public void execLoad() throws ProcessingException { 
    IPersonService service = SERVICES.getService(IPersonService.class); 
    PersonFormData formData = new PersonFormData(); 
    exportFormData(formData); 
    formData = service.load(formData); 
    importFormData(formData); 
    setEnabledPermission(new UpdatePersonPermission()); 
   } 
 
   @Override 
   public void execStore() throws ProcessingException { 
    IPersonService service = SERVICES.getService(IPersonService.class); 
    PersonFormData formData = new PersonFormData(); 
    exportFormData(formData); 
    formData = service.store(formData); 
   } 
 }

In the ”My Contacts” client application the person form is started via two separate form handlers called by the person page context menus. The new handler is used to start the form in the mode to create new persons while the modify handler is implemented to start the form for updating existing persons. When we look at the implementation of the modify handler provided in Listing 5.25, we see the two methods execLoad and execStore.

As already described in Section 3.2.3 of the ”Hello World” tutorial, execLoad is called by the Scout framework the the form is started. Similarly, method execStore is called by the Scout framework after the user has clicked the OK button of a form. In both methods, a corresponding service operation of the PersonService is called. From the form handler’s execLoad method the method load of the person service is called. And the form handler’s execStore method calls the person service’s method store as shown in Listing 5.26.


Listing 5.26: The load and the store methods of the server’s PersonService.
 
public class PersonService extends AbstractService implements IPersonService { 
 @Override 
 public PersonFormData load(PersonFormData formData) throws ProcessingException { 
   if (!ACCESS.check(new ReadPersonPermission())) { 
    throw new VetoException(TEXTS.get("AuthorizationFailed")); 
   } 
 
   SQL.selectInto("SELECT " 
      + "picture_url, " 
      + "first_name, " 
      + "last_name, " 
      + "company_id, " 
      + "headline, " 
      + "location, " 
      + "date_of_birth " 
      + "FROM PERSON " 
      + "WHERE PERSON_ID = :personId " 
      + "INTO " 
      + ":pictureUrl, " 
      + ":firstName, " 
      + ":lastName, " 
      + ":company, " 
      + ":headline, " 
      + ":location, " 
      + ":dateOfBirth", 
      formData); 
 
   return formData; 
 } 
 
 @Override 
 public PersonFormData store(PersonFormData formData) throws ProcessingException { 
   if (!ACCESS.check(new UpdatePersonPermission())) { 
    throw new VetoException(TEXTS.get("AuthorizationFailed")); 
   } 
   SQL.update("UPDATE PERSON SET " 
      + "picture_url = :pictureUrl, " 
      + "first_name = :firstName, " 
      + "last_name = :lastName, " 
      + "headline = :headline, " 
      + "company_id = :company, " 
      + "location = :location, " 
      + "date_of_birth = :dateOfBirth " 
      + "WHERE person_id = :personId ", 
      formData); 
 
   return formData; 
 } 
}

For the load method in Listing 5.26 it is assumed that the in the provided parameter formData the person’s id is available as a search criteria. It might be interesting to note that for the SQL SELECT and UPDATE statements we can provide the complete form data to use Scout’s support for bind variables.

The implementation of the person form’s new handler is implemented similar to it’s modify handler. Instead of calling the service operations load and store, the methods prepareCreate and create are used. In principle, method prepareCreate is not needed for the ”My Contacts” application. As its implementation has been created by the Scout SDK wizard, it does not any harm when the method is left as it is.


Listing 5.27: The create method of the server’s PersonService.
 
public class PersonService extends AbstractService implements IPersonService { 
 @Override 
 public PersonFormData create(PersonFormData formData) throws ProcessingException { 
   if (!ACCESS.check(new CreatePersonPermission())) { 
    throw new VetoException(TEXTS.get("AuthorizationFailed")); 
   } 
 
   if (StringUtility.isNullOrEmpty(formData.getPersonId())) { 
    formData.setPersonId(UUID.randomUUID().toString()); 
   } 
 
   SQL.insert("INSERT INTO PERSON (person_id) " 
      + "SELECT :personId " 
      + "FROM PERSON " 
      + "WHERE person_id = :personId " 
      + "HAVING count(*) = 0", 
      formData); 
 
   return store(formData); 
 } 
}

In Listing 5.27 the implementation of the PersonService’s create method is provided. First, the implementation checks if the provided form data contains a person id. If the person has been entered manually a value for this id is missing and a new id is provided using formData.setPersonId(UUID.randomUUID().toString()). The only responsibility of the INSERT statement following the person id check is to make sure we will have a row in the PERSON table for the person to be created. To save all other form data parameters, we just need to call the previously implemented store method.

5.9 Creating the Company Form

Creating the company form and the necessary backend services is not described here. Instead this task is left as an exercise to the reader and in the text below some minimal guidelines are provided.

The main trick is to start with the New Form wizard to create the company form. This will create all necessary artefacts including the forms, necessary server services, and communication between the client and the server. Then, make sure you don’t forget to add a variable companyId to the company form. To decide on the fields that need to be on the company form you may check the setup of the database schema provided in Listing 5.9. If in doubt about what to do, please refer to the procedure used to create the person form.

In case you get lost completely, you may download the ”My Contacts” application from this books Github repository as described in the Scout wiki14 .

5.10 Adding the Scribe Library to the Application

To access data from sites such as LinkedIn, Xing, Google+ or Facebook, most social networks provide an API that requires user authentication. The current defacto standard for such authentication is the Open Authentication Protocol (OAuth)15

The big advantage of the OAuth standard is the access token based authentication. If an application such as the ”My Contacts” example is in possession of an access token, it can fetch data from the hosting site or even act on the users behalf. It is important to note that the access token is just constructed of two values, the token and the secret. And both values are completely separate from the user’s username and password credentials. This means that an access token may be safely stored in other applications. Should such an application’s data get compromised, the username and password are still save. The user then has the option to remove the compromised access token.

For the ”My Contacts” application we use the Scribe Java library16 This library makes accessing social services very simple. At the same time the Scribe library is used here to demonstrate the integration of external Java libraries in Scout applications.


PIC  

Figure 5.35: Add a new bundle to hold the Scribe JARs.



PIC   PIC

Figure 5.36: Specify the JARs to be contained in the library bundle and the library name.


As we will use the Scribe library only on the Scout server side start the Scout SDK New Library Bundle wizard below the blue server node as shown in Figure 5.35. In the first wizard step shown on the left side of Figure 5.36 add the files scribe-1.3.0.jar and commons-codec-1.8.jar17 using the Add button. Once the two JAR files are listed in the library list of the wizard click the Next button. On the second wizard step, enter ”org.scribe” into the Bundle Name field as shown on the right side of Figure 5.36. Then, close the wizard with the Finish button.

The Scout SDK wizard then creates the corresponding library plugin and updates the server product files and the plugin dependencies of the applications server plugin accordingly. Once the wizard has completed the classes defined in the two JAR files can directly be accessed from the ”My Contact” application’s server plugin.

5.11 Integrating LinkedIn Access with Scribe


Listing 5.28: The LinkedInService service with it’s initializeService method defined in the IService interface.
 
public class LinkedInService extends AbstractService implements ILinkedInService { 
 private final static String LINKEDIN_TOKEN = "linkedin_access_token"; 
 private final static String LINKEDIN_SECRET = "linkedin_access_secret"; 
 private final static String LINKEDIN_CONNECTIONS = "http://api.linkedin.com/v1/people/~/connections"; 
 
 private static IScoutLogger LOG = ScoutLogManager.getLogger(LinkedInService.class); 
 private OAuthService m_service = null; 
 
 @Override 
 public void initializeService(ServiceRegistration registration) { 
   super.initializeService(registration); 
 
   m_service = new ServiceBuilder() 
      .provider(LinkedInApi.class) 
      .apiKey("CiEgwWDkA5BFpNrc0RfGyVuSlOh4tig5kOTZ9q97qcXNrFl7zqk-Ts7DqRGaKDCV") 
      .apiSecret("dhho4dfoCmiQXrkw4yslork5XWLFnPSuMR-8gscPVjY4jqFFHPYWJKgpFl4uLTM6") 
      .build(); 
 }

To access the LinkedIn data with Scribe a separate service LinkedInService is created in the Services folder under the blue server node. Before we add any service operations to the LinkedIn service, the service initialization method is implemented according to Listing 5.28. This initialization is not discussed further here. Instead, the interested reader is referred to the corresponding LinkedInExample on the Scribe web pages18 .


PIC   PIC

Figure 5.37: Add the operation to retrieve an authentication URL.



Listing 5.29: The getAuthUrl method of the LinkedIn service.
 
 @Override 
 public String[] getAuthUrl() throws ProcessingException { 
   Token requestToken = m_service.getRequestToken(); 
   String authLink = m_service.getAuthorizationUrl(requestToken); 
   return new String[]{requestToken.getToken(), requestToken.getSecret(), authLink}; 
 }

The first service operation getAuthUrl is added according to Figure 5.37. This operation will return the necessary information to create a request token and provides a link to open in a web browser to start the authentication against the LinkedIn account. For its implementation see Listing 5.29.


PIC   PIC

Figure 5.38: Add the operations to refresh the access token and to update the LinkedIn contacts.



Listing 5.30: The refreshToken method is used to create a new access token to fetch data from the LinkedIn API.
 
 @Override 
 public void refreshToken(String token, String secret, String securityCode) throws ProcessingException { 
   // retrieve linked in access token 
   Token accessToken = m_service.getAccessToken(new Token(token, secret), new Verifier(securityCode)); 
 
   // make sure current user has param records for linkedin token 
   String userName = ServerSession.get().getUserId(); 
 
   SQL.insert("INSERT INTO USERS_PARAM (username, param) (" 
      + "SELECT :username as username, :param as param " 
      + "FROM USERS_PARAM " 
      + "WHERE username = :username AND param = :param HAVING count(*) = 0" 
      + ")", new NVPair("username", userName), new NVPair("param", LINKEDIN_TOKEN)); 
 
   SQL.insert("INSERT INTO USERS_PARAM (username, param) (" 
      + "SELECT :username as username, :param as param " 
      + "FROM USERS_PARAM " 
      + "WHERE username = :username AND param = :param HAVING count(*) = 0" 
      + ")", new NVPair("username", userName), new NVPair("param", LINKEDIN_SECRET)); 
 
   // update param records with new access token 
   SQL.update("UPDATE USERS_PARAM set value = :value WHERE param = :param AND username = :username", 
      new NVPair("value", accessToken.getToken()), new NVPair("username", userName), new NVPair("param", LINKEDIN_TOKEN)); 
 
   SQL.update("UPDATE USERS_PARAM set value = :value WHERE param = :param AND username = :username", 
      new NVPair("value", accessToken.getSecret()), new NVPair("username", userName), new NVPair("param", LINKEDIN_SECRET)); 
 }

The next operations we add are used to refresh the users access token stored in the ”My Contacts” application and to refresh or download the users contacts stored in the user’s LinkedIn account. See Figure 5.38 for the necessary details for the creation of the two methods.

Method refreshToken is implemented according to Listing 5.30. Using the provided token parameters and the security code, the access token is created using m_service.getAccessToken(...). Then, the user id of the currently logged in user is obtained with ServerSession.get().getUserId(). Once we have both the access token and the user id available in method refreshToken, we can store the token and the secret values for the current user in table USERS_PARAM according to Listing 5.30.

In the current implementation of the ”My Contacts” application this id will always be set to anonymous. The reason for this is the AnonymousSecurityFilter security filter that is activated during the initial project creation step. As for logging, and database access, the configuration of the security filters of the server application is defined in the server’s config.ini file.

org.eclipse.scout.http.servletfilter.security.AnonymousSecurityFilter#active=true


Listing 5.31: The default configuration of the basic security filter in the Scout server’s config.ini file.
 
### Servlet Filter Runtime Configuration 
org.eclipse.scout.http.servletfilter.security.BasicSecurityFilter#active=false 
org.eclipse.scout.http.servletfilter.security.BasicSecurityFilter#realm=contacts Development 
org.eclipse.scout.http.servletfilter.security.BasicSecurityFilter#users=admin\=manager,allen\=allen,blake\=blake

If you would like to change the ”My Contacts” application to a multi-user setup, the next simpler option is to deactivate the anonymous security filter and use the basic security filter instead. In the server’s config.ini file the necessary config lines are already prepared as shown in Listing 5.31. Just set BasicSecurityFilter#active=true and ”My Contacts” the client application will prompt you with a login dialog. In this dialog you may enter one of the login credentials specified in in the BasicSecurityFilter#users parameter of the config.ini file. To log in as user admin, enter password ”manager”. To add new users, just amend the BasicSecurityFilter#users parameter with additional comma separated entries of the form username
password.


Listing 5.32: The updateContacts method is used to enter/update existing contacts based on new data fetched from LinkedIn.
 
 @Override 
 public void updateContacts() throws ProcessingException { 
   try { 
    IPersonService service = SERVICES.getService(IPersonService.class); 
    NodeList persons = readContacts(); 
 
    for (int i = 0; i < persons.getLength(); i++) { 
      if (persons.item(i) instanceof Element) { 
       Element person = (Element) persons.item(i); 
       LOG.info(DomUtility.getString(person)); 
 
       // load existing person data 
       PersonFormData formData = new PersonFormData(); 
       formData.setPersonId(DomUtility.getValue(person, "id")); 
       service.load(formData); 
 
       formData.getPictureUrl().setValue( 
          DomUtility.getValue(person, "picture-url")); 
       formData.getFirstName().setValue( 
          DomUtility.getValue(person, "first-name")); 
       formData.getLastName().setValue( 
          DomUtility.getValue(person, "last-name")); 
       formData.getHeadline().setValue( 
          StringUtility.substring(DomUtility.getValue(person, "headline"), 0, 64)); 
       formData.getLocation().setValue( 
          DomUtility.getValue(person, "location", "name")); 
 
       // save new/updated person data 
       service.create(formData); 
      } 
    } 
   } 
   catch (Exception e) { 
    throw new ProcessingException("LinkedIn Error", e); 
   } 
 }

The implementation of service method updateContacts of the LinkedIn service is provided in Listing 5.32. First, a service reference is obtained to the person process service. Then, the LinkedIn contacts obtained by method readContacts are stored in a persons list object. For each person, a form data is created and its person id parameter is set to DomUtility.getValue(person, "id"). Using the load method of the person process service all attributes of the person are loaded from the server’s database. The new attribute values obtained from the corresponding LinkedIn contact are then used to update the corresponding form data parameters. The newly updated person form data is then stored in the database with service.create(formData). Before we go through the readContacts method, we have a look at the explicit format of the contact data provided by the LinkedIn API.


Listing 5.33: A sample contact represented in the XML format provided by the LinkedIn API.
<?xml version="1.0" encoding="UTF-16"?> 
<person> 
   <id>f7R6wGcblj</id> 
   <first-name>Mike</first-name> 
   <last-name>Milinkovich</last-name> 
   <headline>Executive Director at Eclipse Foundation</headline> 
   <picture-url>http://m3.licdn.com/mpr/mprx/0_IUM7Se9vBU...SbRbQZ4</picture-url> 
   <site-standard-profile-request> 
    <url>http://www.linkedin.com/profile/view?id=14949387...05720*s114280*</url> 
   </site-standard-profile-request> 
   <location> 
    <name>Ottawa, Canada Area</name> 
    <country> 
      <code>ca</code> 
    </country> 
   </location> 
   <industry>Computer Software</industry> 
 </person>


Listing 5.34: The DomUtility class provides functions to parse the XML data structure provided by the LinkedIn API.
 
public class DomUtility { 
 private static Node getSubElement(Node parent, String name) { 
   NodeList nodes = ((Element) parent).getElementsByTagName(name); 
   if (nodes.getLength() == 0) { 
    return null; 
   } 
   return nodes.item(0); 
 } 
 
 public static String getValue(Node node, String name) { 
   Node n = getSubElement(node, name); 
   if (n == null) { 
    return ""; 
   } 
   return n.getTextContent(); 
 } 
 
 public static String getValue(Node node, String name, String subName) { 
   Node n = getSubElement(node, name); 
   if (n == null) { 
    return ""; 
   } 
   return getValue(n, subName); 
 } 
 
 public static String getString(Element element) { 
   Document document = element.getOwnerDocument(); 
   DOMImplementationLS domImplLS = (DOMImplementationLS) document.getImplementation(); 
   LSSerializer serializer = domImplLS.createLSSerializer(); 
   return serializer.writeToString(element); 
 } 
}

From the sample XML extract provided in Listing 5.33, we can readily identify the person id f7R6wGcblj, the first name Mike and some other attribute names with associated attribute values. A closer look reveals the identity of the person to be Mike Milinkovich, a well known character in the Eclipse community. For each LinkedIn contact this format is parsed in method readContacts into a DOM tree19 . Using the DomUtility helper class provided in Listing 5.34, these attribute values are then extracted from this DOM tree and used to update the existing person attributes.


Listing 5.35: The readContacts method to fetch the users connection using the LinkedIn API. The necessary access token is created in method getToken based on the information stored in the database for the logged in user.
 
 private NodeList readContacts() throws Exception { 
   // create singned linkedin request and get response 
   OAuthRequest request = new OAuthRequest(Verb.GET, LINKEDIN_CONNECTIONS); 
   m_service.signRequest(getToken(), request); 
   Response response = request.send(); 
 
   // parse linkedin response stream 
   DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 
   Document document = builder.parse(response.getStream()); 
   Element element = document.getDocumentElement(); 
 
   // basic error handling 
   if (element.getNodeName().equals("error")) { 
    LOG.error(DomUtility.getString(element)); 
    throw new ProcessingException(DomUtility.getString(element)); 
   } 
 
   return element.getChildNodes(); 
 } 
 
 private Token getToken() throws ProcessingException { 
   String userName = ServerSession.get().getUserId(); 
   StringHolder value = new StringHolder(); 
   StringHolder secret = new StringHolder(); 
 
   SQL.selectInto("SELECT value INTO :value FROM USERS_PARAM WHERE param = :param AND username = :username", 
      new NVPair("value", value), new NVPair("param", LINKEDIN_TOKEN), new NVPair("username", userName)); 
   SQL.selectInto("SELECT value INTO :secret FROM USERS_PARAM WHERE param = :param AND username = :username", 
      new NVPair("secret", secret), new NVPair("param", LINKEDIN_SECRET), new NVPair("username", userName)); 
 
   if (StringUtility.isNullOrEmpty(value.getValue())) { 
    throw new ProcessingException("No valid LinkedIn token stored for user " + userName + "’. Please (re)create a token"); 
   } 
 
   return new Token(value.getValue(), secret.getValue()); 
 } 
}

Accessing the user’s LinkedIn contact list is implemented in method readContacts according to Listing 5.35. In this method, a signed OAuth request is first created using the access token provided by method getToken. The request specified by constant LINKEDIN_CONNECTIONS is defined as http://api.linkedin.com/v1/people/~/connections. According to the API specification20 this returns all connections of the user. To sign the request, the access token is retrieved from the database of the ”My Contact” application using method getToken. According to the implementation provided in Listing 5.35 the user id is first obtained from the user’s server session. The necessary parameters to create the access token are then retrieved from the USERS_PARAM table. We have now implemented all necessary server services and operations to access the LinkedIn API, to create/refresh access tokens and to retrieve the users contacts.

5.12 Fetching Contacts from LinkedIn


PIC   PIC

Figure 5.39: Add the form to refresh the LinkedIn access token.


The last piece that is missing to complete the ”My Contacts” application is the user interaction for the LinkedIn integration implemented in the previous section. To allow the user to create/refresh an access token, we need to implement the refresh token form shown in Figure 5.2 at the beginning of this chapter. To create the form code, we use the New Form wizard of the Scout SDK as shown in Figure 5.39. For the Name field enter a new translated text ”Refresh LinkedIn Token” and fill ui.forms into the Sub Package field field. Then, click the Next button to switch to the second wizard step. Here, deselect the elements that will not be needed for the implementation of the refresh form according to the right hand side of Figure 5.39. Once the form has been created by the wizard, add the a Token and a Secret form variable of type string under the form’s Variables folder.


PIC   PIC

Figure 5.40: The token form in the explorer and the properties of the security code field.


The next step is to add the desired layout and the necessary form fields. First, use the New Form Field wizard to add a new group box TokenBox to the MainBox node. And into the TokenBox add a ”Security Code” string field as shown on the left hand side of Figure 5.40. To provide a click-able link that opens a web browser with the LinkedIn authentication link, add a ”Open Auth URL” link of type LinkButton to the main box. To make the user to first click the authentication link button before he tries to fill any content into the security code field we initially disable this field. For this, first click on the security code field in the Scout Explorer and then open the Advanced Properties section of the fields Scout Object Properties. As shown on the right side of Figure 5.40, deselect the Enabled property.


Listing 5.36: The structure of the refresh token form. The security code field gets enabled in method execClickAction after the authentication link button is pressed.
 
public class RefreshTokenForm extends AbstractForm { 
 
 private String m_token; 
 @Order(10.0) 
 public class MainBox extends AbstractGroupBox { 
 
   @Order(10.0) 
   public class TokenBox extends AbstractGroupBox { 
 
    @Order(10.0) 
    public class SecurityCodeField extends AbstractStringField { 
 
      @Override 
      protected boolean getConfiguredEnabled() { 
       return false; 
      } 
 
      @Override 
      protected String getConfiguredLabel() { 
       return TEXTS.get("SecurityCode"); 
      } 
    } 
   } 
 
   @Order(20.0) 
   public class OpenAuthURLButton extends AbstractLinkButton { 
 
    @Override 
    protected String getConfiguredLabel() { 
      return TEXTS.get("OpenAuthURL"); 
    } 
 
    @Override 
    protected void execClickAction() throws ProcessingException { 
      String[] auth = SERVICES.getService(ILinkedInService.class).getAuthUrl(); 
      setToken(auth[0]); 
      setSecret(auth[1]); 
 
      SERVICES.getService(IShellService.class).shellOpen(auth[2]); 
      getSecurityCodeField().setEnabled(true); 
    } 
   }

The implementation of the forms’ structure is provided in Listing 5.36. In the execClickAction method of the link button OpenAuthURLButton the necessary information for the auth link is obtained from the server. For this, we are using the getAuthUrl operation implemented in the previous section. The parameters to create the request token are saved in the form’s token and secret variables. Then, the auth link is opened in a web browser with the shellOpen method of the client’s shell service. Finally, after opening the web browser we can enable the security field with getSecurityCodeField().setEnabled(true).


PIC   PIC

Figure 5.41: Add the menu to open the LinkedIn token form.


We can now integrate the form to refresh the LinkedIn access token under the applications ”File” menu. This is done using the New Menu wizard as shown in Figure 5.41. Enter the translated text ”Refresh LinkedIn Token ...” into the wizard’s Name field and pick the element Exit Menu [before] from the dropdown box of the Sibling field. In the Form to start field select the newly created refresh token form and use the NewHandler entry in the Form Handler field. To close the wizard, click the Finish button.


Listing 5.37: The menu to refresh the LinkedIn token starts the token form and then sends token parameters with the new security code to the LinkedIn backend service.
 
   @Order(10.0) 
   public class RefreshLinkedInToken_Menu extends AbstractExtensibleMenu { 
 
    @Override 
    protected String getConfiguredText() { 
      return TEXTS.get("RefreshLinkedInToken_"); 
    } 
 
    @Override 
    protected boolean getConfiguredEmptySpaceAction() { 
      return true; 
    } 
 
    @Override 
    protected void execAction() throws ProcessingException { 
      RefreshTokenForm form = new RefreshTokenForm(); 
      form.startNew(); 
      form.waitFor(); 
      if (form.isFormStored()) { 
       String token = form.getToken(); 
       String secret = form.getSecret(); 
       String securityCode = form.getSecurityCodeField().getValue(); 
       SERVICES.getService(ILinkedInService.class).refreshToken(token, secret, securityCode); 
      } 
    } 
   }

The implementation of the refresh menu is shown in Listing 5.38. In method execAction(), we need to amend the part after starting the refresh token form. If the method form.isFormStored() returns true, the user has modified the secure code field and it is fair to assume that the user wants to create/refresh his LinkedIn access token. For this, we first retrieve the necessary parameters to call the backend service operation refreshToken implemented in the previous chapter.


PIC   PIC

Figure 5.42: Add the menu to update the LinkedIn contacts.



Listing 5.38: The menu to update the stored persons with current LinkedIn data.
 
   @Order(10.0) 
   public class UpdateLinkedInContactsMenu_ extends AbstractExtensibleMenu { 
 
    @Override 
    protected String getConfiguredText() { 
      return TEXTS.get("UpdateLinkedInContacts"); 
    } 
 
    @Override 
    protected void execAction() throws ProcessingException { 
      Desktop.this.setStatusText(TEXTS.get("ImportingLinkedIn")); 
      SERVICES.getService(ILinkedInService.class).updateContacts(); 
      Desktop.this.setStatusText(null); 
    } 
   }

As the last missing component of the ”My Contacts” application, we add the menu to fetch the LinkedIn contacts and update the database accordingly. The menu entry is created as a sub menu of the ”Tools” menu. For this, use the new menu wizard on the ToolsMenu node as shown in Figure 5.42. The implementation of the menu’s execAction method shown in Listing 5.38 is trivial. We only need to call the operation of the LinkedInService implemented in the server part of the ”My Contacts” application.