My Tutorial
My Tutorial
Rufus Credle Jeff Coster Steffen Eckardt Girish Gopinathan Vani Mittal Juha Nevalainen Ramya Rajendiran
ibm.com/redbooks
International Technical Support Organization WebSphere Commerce Line-of-Business Tooling Customization November 2008
SG24-7619-00
Note: Before using this information and the product it supports, read the information in Notices on page ix.
First Edition (November 2008) This edition applies to IBM WebSphere Application Server Network Deployment V6.0.2, IBM WebSphere Commerce V6.0.0, WebSphere Commerce Developer V6.0.0, and IBM WebSphere Commerce Feature Pack 3 Version 3.0.1
Copyright International Business Machines Corporation 2008. All rights reserved. Note to U.S. Government Users Restricted Rights -- Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
Contents
Notices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix Trademarks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . x Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi The team that wrote this book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xii Become a published author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiv Comments welcome. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv Part 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Chapter 1. IBM Management Center for WebSphere Commerce . . . . . . . . 3 1.1 Why Management Center was introduced . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.2 Management Center capabilities. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2.1 Functional capabilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2.2 Usability capabilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.2.3 Finding detailed information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.3 Comparing the Management Center to other WebSphere Commerce line-of-business tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.4 Management Center technology . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 1.4.1 Client-side technology . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 1.4.2 Server-side technology . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Chapter 2. Overview of the IBM Management Center for WebSphere Commerce environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.1 High-level SOA architecture for the Management Center . . . . . . . . . . . . . 16 2.2 Presentation layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2.2.1 Overview of the Presentation layer . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2.2.2 The OpenLaszlo application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.2.3 The Mediation layer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.3 Business Logic layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.3.1 Components of the Business Logic layer . . . . . . . . . . . . . . . . . . . . . 23 2.3.2 Business Object Mediators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Part 2. IBM Management Center for WebSphere Commerce customization . . . . . . . . . . . 27 Chapter 3. Customization overview. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.1 Development assets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 3.1.1 Management Center user interface. . . . . . . . . . . . . . . . . . . . . . . . . . 30 3.1.2 Management Center Web application . . . . . . . . . . . . . . . . . . . . . . . . 43
iii
3.1.3 WebSphere Commerce services . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Chapter 4. Planning and designing customizations . . . . . . . . . . . . . . . . . 49 4.1 Who should read this chapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 4.2 Knowledge prerequisites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 4.3 Overview of this chapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 4.4 Customization methodology . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 4.5 Solution Outline phase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 4.5.1 Gathering requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 4.5.2 Defining the application model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 4.6 Macro Design phase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 4.6.1 Refining the requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 4.6.2 Setting up the environments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 4.6.3 Defining the processes and guidelines . . . . . . . . . . . . . . . . . . . . . . . 57 4.6.4 Defining the test specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 4.7 Micro Design phase. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 4.8 Build Cycle phase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 4.8.1 Coding and unit testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 4.8.2 Performing system testing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 4.8.3 Developing support materials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 4.9 Deployment phase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 4.9.1 Setting up the production environment . . . . . . . . . . . . . . . . . . . . . . . 63 4.9.2 Deploying the customizations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 4.10 Locating Management Center customization within a WebSphere Commerce project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 Chapter 5. Development tools and extension deployment . . . . . . . . . . . . 67 5.1 Development tools. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 5.1.1 WebSphere Commerce Developer . . . . . . . . . . . . . . . . . . . . . . . . . . 68 5.1.2 Management Center assets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 5.2 Deploying the extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 5.2.1 Exporting the extension code from the development environment . . 71 5.2.2 Backing up the application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 5.2.3 Deploying the WAR module to the target WebSphere Commerce server 72 5.2.4 Testing the changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 5.3 Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 5.3.1 Tracing your own components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 5.4 Debugging server communication with the TCP/IP Monitor . . . . . . . . . . . 78 5.4.1 Creating the TCP/IP monitor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 5.5 Hints and tips. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 5.5.1 Turning automatic build off . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 5.5.2 Using a version control system . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
iv
5.5.3 Increasing the Management Center inactivity time-out . . . . . . . . . . . 82 5.5.4 Customizing your Workbench perspective . . . . . . . . . . . . . . . . . . . . 83 5.5.5 Using the XML editor for OpenLaszlo files . . . . . . . . . . . . . . . . . . . . 83 5.5.6 Disabling the tools that you are using . . . . . . . . . . . . . . . . . . . . . . . . 83 5.6 Problem determination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 5.6.1 What to do if your changes are not appearing? . . . . . . . . . . . . . . . . 84 5.6.2 What to do if the server fails to start successfully . . . . . . . . . . . . . . . 85 Part 3. Customization scenario examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 Chapter 6. Enabling the merchandising associations Semantic Attribute . 89 6.1 Understanding the extension requirement . . . . . . . . . . . . . . . . . . . . . . . . 91 6.2 Checking if the semantic specifier element was provided . . . . . . . . . . . . . 91 6.3 Defining an extended resource bundle and properties file . . . . . . . . . . . . 92 6.3.1 Registering the new properties in the resource bundle . . . . . . . . . . . 94 6.4 Adding the extensions to the tool extension library . . . . . . . . . . . . . . . . . . 95 6.5 Adding the Semantic property to the Merchandising Association class . . 96 6.6 Implementing a Validator. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 6.7 Including the semantic specifier in the JSP. . . . . . . . . . . . . . . . . . . . . . . 100 6.8 Populating database tables with custom semantics . . . . . . . . . . . . . . . . 101 6.9 Testing the extension . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 Chapter 7. Adding a new service action . . . . . . . . . . . . . . . . . . . . . . . . . . 107 7.1 Defining the properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 7.2 Customizing the product list view . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 7.3 Updating the catalog entry grid view . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 7.4 Providing the buyable property value definitions. . . . . . . . . . . . . . . . . . . 112 7.5 Defining the Struts action . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 7.5.1 Providing the response JSP files . . . . . . . . . . . . . . . . . . . . . . . . . . 114 7.6 Testing the extension . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 Chapter 8. Advanced search for merchandising associations . . . . . . . . 119 8.1 Understanding the requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 8.2 Building the customization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 8.2.1 Customizing the Management Center user interface . . . . . . . . . . . 123 8.2.2 Customizing the Management Center Web application . . . . . . . . . 129 8.2.3 Customizing WebSphere Commerce services . . . . . . . . . . . . . . . . 134 8.3 Testing the customization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 Chapter 9. Price comparison mashup. . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 9.1 Understanding the requirement. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 9.2 Price comparison service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 9.3 Building the customization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Contents
9.3.1 Resource bundle definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 9.3.2 Price comparison class definition . . . . . . . . . . . . . . . . . . . . . . . . . . 150 9.3.3 Property component definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 9.3.4 Updating the product properties view . . . . . . . . . . . . . . . . . . . . . . . 159 9.3.5 Updating the library file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 9.4 Testing the customization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 Chapter 10. Customizing user preferences . . . . . . . . . . . . . . . . . . . . . . . 165 10.1 Understanding the requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 10.2 Building the customization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 10.2.1 Customizing the client side . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 10.2.2 Customizing the server side . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176 10.2.3 Testing the customization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 Chapter 11. Spell Checker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 11.1 Understanding the requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180 11.2 Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183 11.2.1 Meeting requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183 11.2.2 CDYNE service detail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 11.2.3 Component design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 11.2.4 Visual Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 11.3 Deploying the complete or staged examples . . . . . . . . . . . . . . . . . . . . 190 11.4 Staged development . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 11.4.1 Explaining the visual prototype stage (example 1) . . . . . . . . . . . . 194 11.4.2 Viewing the visual prototype stage (example 1) . . . . . . . . . . . . . . 204 11.4.3 Explaining the static XML prototype stage (example 2) . . . . . . . . 206 11.4.4 Viewing the static XML prototype stage (example 2) . . . . . . . . . . 209 11.4.5 Explaining the static XML prototype stage (example 3) . . . . . . . . 209 11.4.6 Viewing the static XML prototype stage (example 3) . . . . . . . . . . 210 11.4.7 Explaining the property provided prototype stage (example 4) . . . 211 11.4.8 Testing the property provided prototype stage (example 4) . . . . . 213 11.4.9 Explaining the CDYNE service prototype stage (example 5) . . . . 216 11.4.10 Viewing the CDYNE service prototype stage (example 5) . . . . . 218 11.4.11 Explaining the highlighted text prototype stage (example 6). . . . 219 11.4.12 Viewing the highlighted text prototype stage (example 6). . . . . . 222 11.4.13 Explaining the final stage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223 11.4.14 Testing the final stage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234 11.5 Possible enhancements and changes. . . . . . . . . . . . . . . . . . . . . . . . . . 235 11.5.1 Dictionary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 11.5.2 Replace /skip single occurrence . . . . . . . . . . . . . . . . . . . . . . . . . . 236 11.5.3 Using a different service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 11.6 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 Chapter 12. Custom Application Configuration Management tool. . . . . 239
vi
12.1 Understanding the requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240 12.2 Overview of the customization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 12.2.1 Architectural overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 12.3 Customizing the Management Center user interface . . . . . . . . . . . . . . 248 12.3.1 Main tool definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249 12.3.2 Object definitions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 12.3.3 Search definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259 12.3.4 List definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261 12.3.5 Filter definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265 12.3.6 Property view definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265 12.3.7 Resource bundle definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275 12.3.8 Image files for Custom Application Configuration Management tool . 276 12.3.9 Library file definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276 12.3.10 Adding the Custom Application Configuration Management tool to Management Center . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 12.4 Customizing the WebSphere Commerce services . . . . . . . . . . . . . . . . 280 12.4.1 Preparing your development environment . . . . . . . . . . . . . . . . . . 282 12.4.2 Generating the CustomAppProperties service module projects . . 283 12.4.3 Defining the CustomAppProperties noun . . . . . . . . . . . . . . . . . . . 286 12.4.4 Generating the SDOs for the CustomAppProperties noun . . . . . . 298 12.4.5 Generating the Persistence layer for the CustomAppProperties service module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303 12.4.6 Understanding the assets of the CustomAppProperties-Server project 309 12.4.7 Updating the Data Service configuration. . . . . . . . . . . . . . . . . . . . 313 12.4.8 Implementing the Business Object Mediators to mediate logical and physical SDOs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313 12.4.9 Configuring the Data Service layer for the CustomAppProperties service module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346 12.4.10 Implementing the Business Logic layer. . . . . . . . . . . . . . . . . . . . 356 12.4.11 Implementing the client library . . . . . . . . . . . . . . . . . . . . . . . . . . 362 12.4.12 Implementing access control. . . . . . . . . . . . . . . . . . . . . . . . . . . . 363 12.4.13 Implementing unit tests using the CustomAppProperties client library 371 12.4.14 Deploying to the WebSphere Commerce server. . . . . . . . . . . . . 392 12.5 Customizing Management Center Web application . . . . . . . . . . . . . . . 393 12.5.1 Configuring the client-service-mapping . . . . . . . . . . . . . . . . . . . . . 394 12.5.2 Configuring the client-service-mapping for Get commands . . . . . 396 12.5.3 Configuring views and commands . . . . . . . . . . . . . . . . . . . . . . . . 399 12.5.4 Implementing object mapping JSPs for Get commands . . . . . . . . 404 12.5.5 Implementing object mapping JSPs for Process and Change commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
Contents
vii
12.5.6 Deploying the customization to the WebSphere Commerce Test Server 411 12.6 Testing the customization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411 12.6.1 Testing the CustomAppProperties service using unit tests . . . . . . 411 12.6.2 Testing the Management Center customizations . . . . . . . . . . . . . 413 Appendix A. Additional material . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419 Locating the Web material . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419 Using the Web material . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420 How to use the Web material . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420 Importing the Redbook.LOBTools.zip file. . . . . . . . . . . . . . . . . . . . . . . . . . . . 420 Related publications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423 IBM Redbooks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423 Online resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423 How to get Redbooks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432 Help from IBM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 432 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 433
viii
Notices
This information was developed for products and services offered in the U.S.A. IBM may not offer the products, services, or features discussed in this document in other countries. Consult your local IBM representative for information on the products and services currently available in your area. Any reference to an IBM product, program, or service is not intended to state or imply that only that IBM product, program, or service may be used. Any functionally equivalent product, program, or service that does not infringe any IBM intellectual property right may be used instead. However, it is the user's responsibility to evaluate and verify the operation of any non-IBM product, program, or service. IBM may have patents or pending patent applications covering subject matter described in this document. The furnishing of this document does not give you any license to these patents. You can send license inquiries, in writing, to: IBM Director of Licensing, IBM Corporation, North Castle Drive, Armonk, NY 10504-1785 U.S.A. The following paragraph does not apply to the United Kingdom or any other country where such provisions are inconsistent with local law: INTERNATIONAL BUSINESS MACHINES CORPORATION PROVIDES THIS PUBLICATION "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Some states do not allow disclaimer of express or implied warranties in certain transactions, therefore, this statement may not apply to you. This information could include technical inaccuracies or typographical errors. Changes are periodically made to the information herein; these changes will be incorporated in new editions of the publication. IBM may make improvements and/or changes in the product(s) and/or the program(s) described in this publication at any time without notice. Any references in this information to non-IBM Web sites are provided for convenience only and do not in any manner serve as an endorsement of those Web sites. The materials at those Web sites are not part of the materials for this IBM product and use of those Web sites is at your own risk. IBM may use or distribute any of the information you supply in any way it believes appropriate without incurring any obligation to you. Information concerning non-IBM products was obtained from the suppliers of those products, their published announcements or other publicly available sources. IBM has not tested those products and cannot confirm the accuracy of performance, compatibility or any other claims related to non-IBM products. Questions on the capabilities of non-IBM products should be addressed to the suppliers of those products. This information contains examples of data and reports used in daily business operations. To illustrate them as completely as possible, the examples include the names of individuals, companies, brands, and products. All of these names are fictitious and any similarity to the names and addresses used by an actual business enterprise is entirely coincidental. COPYRIGHT LICENSE: This information contains sample application programs in source language, which illustrate programming techniques on various operating platforms. You may copy, modify, and distribute these sample programs in any form without payment to IBM, for the purposes of developing, using, marketing or distributing application programs conforming to the application programming interface for the operating platform for which the sample programs are written. These examples have not been thoroughly tested under all conditions. IBM, therefore, cannot guarantee or imply reliability, serviceability, or function of these programs.
ix
Trademarks
IBM, the IBM logo, and ibm.com are trademarks or registered trademarks of International Business Machines Corporation in the United States, other countries, or both. These and other IBM trademarked terms are marked on their first occurrence in this information with the appropriate symbol ( or ), indicating US registered or common law trademarks owned by IBM at the time this information was published. Such trademarks may also be registered or common law trademarks in other countries. A current list of IBM trademarks is available on the Web at http://www.ibm.com/legal/copytrade.shtml The following terms are trademarks of the International Business Machines Corporation in the United States, other countries, or both: BladeCenter ClearCase Cloudscape DB2 developerWorks IBM Lotus Notes Lotus Notes Rational Redbooks Redbooks (logo) System x WebSphere
The following terms are trademarks of other companies: Adobe Flash, Adobe, and Portable Document Format (PDF) are either registered trademarks or trademarks of Adobe Systems Incorporated in the United States, other countries, or both. Oracle, JD Edwards, PeopleSoft, Siebel, and TopLink are registered trademarks of Oracle Corporation and/or its affiliates. EJB, J2EE, Java, JavaScript, JSP, and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other countries, or both. Windows, and the Windows logo are trademarks of Microsoft Corporation in the United States, other countries, or both. Other company, product, or service names may be trademarks or service marks of others.
Preface
The Management Center for WebSphere Commerce is the next generation business user tool for managing online business tasks. It was introduced with WebSphere Commerce Version 6, Feature Pack 3, for example, catalog tasks, marketing tasks, and promotion tasks. In this IBM Redbooks publication, we give you a broad understanding of the features and capabilities of the Management Center for WebSphere Commerce. In addition, you learn how the Management Center fits into the service-oriented architecture (SOA) framework of IBM WebSphere Commerce. In this book, we: Help you to install, tailor, and configure Management Center to optimize the day-to-day activities of the business users who manage merchandising and marketing tasks for stores that use the consumer direct and Business-to-Business (B2B) business models. Discuss the assets that make up the Management Center and what assets you can customize. Share some of the common customization scenarios. Demonstrate how to plan and perform customizations to the Management Center and how to understand the use of development tools for creating Management Center customizations. Provide examples of the customization scenarios that we developed and tested in the International Technical Support Organization labs and WebSphere Commerce labs, which are available for you to use. The target audience for this book is Commerce Architects, Commerce Developers, Project Managers, and Deployment Managers.
xi
Figure 1 Left to Right: Rufus Credle, Steffen Eckardt, Vani Mittal, Ramya Rajendiran, Jeff Coster, Girish Gopinathan, Juha Nevalainen
This book was produced by a team of specialists from around the world working at the International Technical Support Organization, Raleigh Center. Rufus Credle is a Certified Consulting IT Specialist at the ITSO, Raleigh Center. In his role as Project Leader, he conducts residencies and develops IBM Redbooks publications and IBM Redpaper publications about network operating systems, enterprise resource planning (ERP) solutions, voice technology, high availability, and clustering solutions, Web application servers, pervasive computing, IBM and OEM e-business applications, System x, and IBM BladeCenter. Rufus' various positions during his IBM career include assignments in administration and asset management, systems engineering, sales and marketing, and IT services. He has a BS degree in business management from Saint Augustine's College. Rufus has been employed at IBM for 28 years. Jeff Coster is a Senior IT Specialist, IBM Certified Application Developer WebSphere Commerce Version 6.0, IBM Certified System Administrator WebSphere Commerce Version 6.0 in IBM Hursley UK.
xii
Steffen Eckardt is an Advisory IT Architect from IBM Germany working for IBM Global Business Services. He has over eight years of experience in planning, building, and running WebSphere Commerce applications. Steffen worked in many customer projects as an Application Architect, a Consultant, Technical Project Leader, Lead Developer, and Reviewer. He has a Diploma Degree in Commercial Information Technology from the Technical University of Ilmenau, Germany. Girish Gopinathan is an Advisory Software Engineer in IBM US. He has 12 years of experience in software design and development and has performed WebSphere Commerce customization for IBM.com since 2002. He has a Bachelor of Engineering degree in Electronics and Communication from Karunya Institute of Technology, Comibatore, India. Vani Mittal is an IBM accredited IT Specialist working at IBM India in the India Software Labs. She works as a consultant in the IBM Software Services for WebSphere (ISSW) team. She has over five years of experience in consulting, design, and development of WebSphere Commerce engagements. She has a Master of Computer Applications degree from Jawaharlal Nehru University, New Delhi, India. Juha Nevalainen is a Consulting IT Specialist from the IBM WebSphere Tech Sales team in Finland. He has 12 years of experience with WebSphere Commerce in Finland and in the UK. He has a M.Sc. degree in Computer Science from Helsinki University of Technology. Juhas areas of expertise include WebSphere Commerce deployments, WebSphere Business Process Management, and some of the IBM Rational Application Developer tools. Juha contributed to the RAD Version 6 Programmers Guide Redbook publication. Ramya Rajendiran is a Software Engineer in the IBM India Software Labs, Bangalore. She has two years of experience in the software field with WebSphere Commerce. Her working areas include WebSphere Commerce Content and Catalog Management and Management Center. She has a Bachelor's degree in Engineering in the Computer Science stream from the College of Engineering, Guindy, Anna University. Thanks to the following people for their contributions to this project: Tamikia Barrow, Margaret Ticknor International Technical Support Organization, Raleigh Center Scott Guminy, WebSphere Commerce Architect, IBM Software Group, Application and Integration Middleware Software IBM Canada
Preface
xiii
Daniel Cooper, Software Development - WebSphere Commerce IBM Canada Jacob Vandergoot, IBM Software Group, Application and Integration Middleware Software IBM Canada Rob Leroux, WebSphere Commerce Software Developer IBM Canada
xiv
Comments welcome
Your comments are important to us! We want our books to be as helpful as possible. Send us your comments about this book or other IBM Redbooks in one of the following ways: Use the online Contact us review Redbooks form found at: ibm.com/redbooks Send your comments in an e-mail to: [email protected] Mail your comments to: IBM Corporation, International Technical Support Organization Dept. HYTD Mail Station P099 2455 South Road Poughkeepsie, NY 12601-5400
Preface
xv
xvi
Part 1
Part
Introduction
In part 1 of the book, you will learn about: The IBM Management Center for WebSphere Commerce Why the Management Center was introduced The capabilities of the Management Center How the Management Center fits into the SOA framework of IBM WebSphere Commerce
Chapter 1.
Compared to the existing WebSphere Commerce Tools framework, the number of touch points is significantly reduced. The extensions are now easier to build because they are more constricted by the Management Center framework, which makes the development of artifacts more straightforward. Together with logging, tracing, and debugging support, the effort is less in comparison. The extensive documentation that is available in the WebSphere Commerce Information Center enables IT architects and software developers to get familiar with the correlations behind the scenes because the tutorials give a jump start on the customization road of Management Center. This Redbooks publication is the ultimate guide to Management Center customization work. We recommend that you read this book before you start planning, designing, and implementing any non-simple customization scenario.
Manage descriptive and defining attributes of catalog entries Manage merchandising associations of catalog entries Manage sales catalogs, including creation, update, and deletion
To help you create new promotions that are similar to existing ones, there is a copy and paste function that creates a promotion with the same data set as your promotion blueprint.
1.3 Comparing the Management Center to other WebSphere Commerce line-of-business tools
IBM WebSphere Commerce offers a number of tools for line-of-business users. The following tools are available: WebSphere Commerce Accelerator WebSphere Commerce Accelerator allows you to maintain online stores, hubs, and catalogs by completing various store operations, from managing the look and feel of your store to creating and maintaining orders to tracking store activities. You can, for example, manage the catalogs, create marketing campaigns, and view operational reports. The Accelerator provides most of the functions that Management Center offers too. However, it does not provide the same rich and high-performance user experience that the Management Center provides, but is a matured and widely used tool. While the future direction of the WebSphere Commerce line-of-business tooling will move toward the Management Center, the Accelerator is still fully supported. A number of functions are only available in Accelerator and there will be no replacement within WebSphere Commerce Version 6.
To learn more about WebSphere Commerce Accelerator, visit the WebSphere Commerce Information Center, specifically the WebSphere Commerce Accelerator section: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.user.doc/concepts/ctfmc.htm IBM Management Center for WebSphere Commerce The Management Center is the next generation line-of-business tool for managing business tasks for online businesses. The Management Center allows business users to perform merchandising and marketing tasks to create, update, and maintain merchandising and marketing assets. The Management Center will be the successor of the Accelerator over time. New capabilities introduced in later versions of WebSphere Commerce will be managed using the Management Center. IBM Sales Center for WebSphere Commerce The IBM Sales Center for WebSphere Commerce helps you accomplish your customer-service tasks easily and efficiently. You can work with orders and quotes for many customers and in many stores at the same time. In addition, you can quickly find information and compare products. The IBM Sales Center is the preferred solution for Customer Sales Representatives who require a highly-efficient user interface for day-to-day call center tasks. You can also use it for in-store customer service tasks, where in-store staff can use it to quickly find a user account and work with a customers privileges, for example, promotions and coupons. The Management Center and IBM Sales Center address different tasks, different user roles, in different work locations. Management Center addresses mainly planning and content management tasks on the level of headquarter or departments, while IBM Sales Center is about customer relationship management and order capture near the point-of-sale, either remotely in a call center or locally in a store. To learn more about IBM Sales Center for WebSphere Commerce, visit the WebSphere Commerce Information Center, specifically the Getting started with the IIBM Sales Center section: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.telesales.tsr.doc/misc/welcome.htm Alternatively, you can review the Redbooks publication Deploying and Customizing IBM Sales Center for WebSphere Commerce V6, SG24-7249. Table 1-1 on page 10 provides a side-by-side comparison of all of the tools that we discussed in this section.
Table 1-1 Comparison of the WebSphere Commerce Accelerator, IBM Management Center for WebSphere Commerce, and IBM Sales Center Category WebSphere Commerce Accelerator Line-of-business users performing the following roles: Operational roles Business relationship roles Product management and merchandising roles Marketing roles Customer service roles Headquarter and departments Maintain online stores and hubs that support all aspects of settings, content management, and operations, which includes managing: Hubs Stores Products and catalogs Content Marketing and promotions Business relationships Users Orders Inventory Logistics Auctions Payments RFQs Tax Shipping IBM Management Center for WebSphere Commerce Line-of-business users performing the following roles: Product management and merchandising roles Marketing roles IBM Sales Center
Audience
Headquarter and departments Supports content management, which includes managing: Products and catalogs Content Marketing and promotions
Call center and point-of-sale locations Supports operations, which includes managing: Users Orders
10
Category
WebSphere Commerce Accelerator Traditional browser-based Web application. Uses DHTML, JavaScript code, and CSS.
IBM Management Center for WebSphere Commerce Flash-based rich interface application that runs in a browser. Uses the OpenLaszlo programming model that contains XML declarations and JavaScript code. WebSphere Commerce MVC architecture. Uses WebSphere Commerce Services, which includes OAGIS-based Web services, Business Object Document (BOD) commands, and Service Data Object (SDO)-based Data Service Layer persistence. Multi-tasking user interface that employs sophisticated user controls. The user client interface is loaded only once. Action invocation and data transmission are separated from the user interface behavior.
Client technology
Eclipse-based rich client platform that is installed on a local machine. Uses the Eclipse programming model that contains XML and Java code. WebSphere Commerce MVC architecture. Uses WebSphere Commerce Services, which includes OAGIS-based Web services, BOD commands, and Service Data Object (SDO)-based Data Service Layer persistence. Multi-tasking user interface that employs sophisticated user controls. The user client interface is loaded only once. Action invocation and data transmission are separated from the user interface behavior.
Server technology
WebSphere Commerce MVC architecture. Uses Struts framework, controller and task commands, EJBs, and JSPs. Accepts HTTP POST and GET requests containing structured name-value data.
Single-tasking user interface that employs standard HTML user controls. Performs a full request-response cycle to the server with a page reload on every action.
11
Category
WebSphere Commerce Accelerator Client-side and server-side customizations through changes or enhancements in JSP files, XML files, JavaScript files, and Java code.
IBM Management Center for WebSphere Commerce Client-side customizations through changes or enhancements in Properties files and mainly OpenLaszlo files that contain XML and JavaScript code. Server-side customizations using the WebSphere Commerce Service framework through changes or enhancements in JSP files, XML files, Java code, and Query template files.
Performing customizations
Client-side customizations through changes or enhancements in XML files, Properties files, and Java Code by using the Eclipse framework and Standard Widget Toolkit (SWT) or by using the Sales Center's own customization framework. Server-side customizations using the WebSphere Commerce Service framework through changes or enhancements in JSP files, XML files, Java code, and Query template files. Available in WebSphere Commerce V6.0
12
Management Center for WebSphere Commerce only uses Flash; therefore, users need a supported Internet browser with an installed Flash plug-in. Read Chapter 2, Overview of the IBM Management Center for WebSphere Commerce environment on page 15 for more details.
13
14
Chapter 2.
15
Figure 2-1 Where the Management Center fits in the SOA model
The old style of using name value pair commands should only be used for Management Center customizations where: There is already a name value pair command that fits or is close to the requirements.
16
It is too much of an effort to create a new SOA-based command for the required Management Center changes. For more information about WebSphere Commerce and its use of SOA for the Management Center, visit the following Web site: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/index.jsp?topic= /com.ibm.commerce.developer.doc/concepts/csdsoa.htm
Chapter 2. Overview of the IBM Management Center for WebSphere Commerce environment
17
Presentation layer Web browser OpenLaszlo application http Request XML Response
Mediation layer
BOD Response
Figure 2-2 Presentation layer and its interaction with the Business Logic layer
The Management Center, the OpenLaszlo Application, as seen in Figure 2-2, is a rich Internet applicationa Web application that has rich user interface features and functionalities (of traditional desktop applications). This OpenLaszlo application introduces a layer of code between the user and the server. This layer of code is called the client engine, which you download as part of the instantiation of the Management Center. The client engine essentially takes care of maintaining the client-side data model, rendering the applications user interface and the server communication. A data model is maintained on the client side that helps in transferring and processing the necessary to support the rich user interface features and functionalities to the Web client; however, the bulk of data is maintained on the server. The OpenLaszlo application communicates in the Representational State Transfer (REST) style. It understands and communicates through incoming XML data and outgoing HTTP requests. WebSphere Commerce services understand only BOD requests and responses. The Mediation layer acts as a proxy server to the OpenLaszlo application and accepts the incoming HTTP requests from the OpenLaszlo application to build the appropriate BOD requests from the incoming HTTP requests. The Mediation layer acts on the BOD response from the services to build XML data that the OpenLaszlo application can understand.
18
View
XML Response
For more details about the OpenLaszlo application, visit the following Web site: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/index.jsp?topic= /com.ibm.commerce.management-center_customization.doc/concepts/ctfcmcui .htm
Chapter 2. Overview of the IBM Management Center for WebSphere Commerce environment
19
20
Presentation layer Web browser OpenLaszlo Application HTTP Request Servlet Filter Struts Application Client Library XML Response
Mediation layer
JSPs
BOD Response
Figure 2-4 Mediation layer and its interaction with the Presentation layer and Business Logic layer
Chapter 2. Overview of the IBM Management Center for WebSphere Commerce environment
21
The JSPs interact with the BODs to get the required data and to build the XML response in the form that the OpenLaszlo application requires. These JSPs are often a customization point. The Client Library allows Java or JSP interactions with the BODs. This component is not for customization.
22
Management Center 1 6
Chapter 2. Overview of the IBM Management Center for WebSphere Commerce environment
23
provides a simple consistent interface for all data access, which makes the Business Logic layer independent from the persistence technology that is used. The Data Service Facade is a thin layer that provides a single entry point into DSL. It enables interfaces to work with both physical and logical data model. It also allows each service module to register with the Data Service Layer and loads the service module-specific configuration files.
24
Presentation layer Management Center Business logic layer WebSphere Commerce Services
Business Object Document Processing Commands Logical SDOs Data Service Facade Data Service Layer
Figure 2-7 Overview of Business Logic layer and the Persistence layer
For Management Center customizations, the Business Logic layer is typically less customized in comparison to the Presentation layer; therefore, we provide a limited amount of information about this layer. For detailed descriptions of the Business Logic layer and how to do more extensive customizations, visit: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/index.jsp?topic= /com.ibm.commerce.developer.soa.doc/concepts/csdsoaprogmodel.htm
Chapter 2. Overview of the IBM Management Center for WebSphere Commerce environment
25
26
Part 2
Part
27
customizations to the Management Center and understand the use of development tools and assets for creating Management Center customizations.
28
Chapter 3.
Customization overview
In this chapter, we provide an overview of the assets that make up the Management Center. We discuss which of these assets you can customize and also describe some common customization scenarios. If you plan to customize the Management Center, it is important to understand the framework and the components that make up the Management Center. For an overview of the Management Center framework and development environment see Chapter 1, IBM Management Center for WebSphere Commerce on page 3 and Chapter 2, Overview of the IBM Management Center for WebSphere Commerce environment on page 15.
29
30
Table 3-1 Main application area customization scenarios Scenario Change the layout of the main page Required changes Change the order of wcfBannerArea, wcfMainArea, and wcfStatusArea classes as desired. Reference WebSphere Commerce Information Center: Rearranging the layout of the Management Center main page: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tas ks/ttfmainpagelayout.htm
Status area
The status area is the bottom area in the Management Center shell. By default, it consists of the message link and the progress indicator. The status area is defined in its own OpenLaszlo source file ToolStatusArea.lzx. The class that defines the status area is wcfStatusArea. Table 3-2 lists some customization scenarios for the status area.
Table 3-2 Status area customization scenarios Scenario Change the layout of the status area Required changes Change the order of the wcfProgressIndicator and wcfMessageLink classes. Reference WebSphere Commerce Information Center: Rearranging the layout of the Management Center status area: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tas ks/ttfstatusarealayout.htm
Banner area
The banner area is defined in its own OpenLaszlo source file ToolBannerArea.lzx. By default, it is composed of the application menu, user identification area, logout link, and the branding logo. Table 3-3 on page 32 lists the customization scenarios that require changes to the banner area.
31
Table 3-3 Banner area customization scenarios Scenario Change the layout of the banner area Required changes Change the order of welcomeText, divider1, wcfLogoutLink, divider2, and headerLogo within bannerRightView. Reference WebSphere Commerce Information Center: Rearranging the layout of the Management Center banner: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tas ks/ttfbannerlayout.htm None
Modify the welcomeText element in ToolBannerArea.lzx to use a different resource bundle property. By default the IBM logo is displayed. You can change it to reflect your custom logo.
WebSphere Commerce Information Center: Changing the logo image: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tas ks/ttfchangelogo.htm
Application menu
The application menu provides access to all tools that are built in the system, and items in the menu are visible to you based on the usages that are attached to the associated component. The application menu also provides hyperlinks to WebSphere Commerce Accelerators Getting Started and Preferences. The application menu is defined in its own OpenLaszlo source file, ApplicationMenuItems.lzx. The class wcfApplicationMenuItems defines the menu items. Table 3-4 on page 33 lists the customization scenarios that require changes to the application menu.
32
Table 3-4 Application menu customization scenarios Scenario Change the layout of the application menu Required changes Change the order of the various wcfApplicationMenuItem classes. Reference WebSphere Commerce Information Center: Rearranging the layout of the Management Center menu: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tas ks/ttfappmenulayout.htm Change the icons that represent a tool Change the activeIconSrc and inactiveIconSrc to use your custom icons. WebSphere Commerce v6 Information Center: Changing the tool icon for the Management Center menu: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tas ks/ttfchangeappmenuicon.htm Change the access control for a tool Authorize a new business role to access a tool. WebSphere Commerce v6 Information Center: Changing access control for a Management Center tool: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tas ks/ttfcreatelobrole.htm
Preferences dialog
You can set the following global preferences using the Preference Dialog, which you can open by selecting the Preferences menu item in the tool menu: Default store that is used when the tools are opened Default language to be used in the tool UI Default cultural locale that determines the number format in the tool Whether to enable the Extended help feature in the application or not The UI default language is persisted as the preferred language of the user in the USERS table in the database (USERS.LANGUAGE_ID), and the other preferences are persisted in the database as member attributes on a per user basis. So when you try to save the preferences by clicking the OK button in the preferences dialog, the member service is called to persist the preferences to the database. Table 3-5 on page 34 lists all of the classes that are relevant to the Preferences feature.
33
Table 3-5 User preferences API classes Class wcfUserPreferenceDialog Source file UserPreferenceDialog.lzx Description This is the user preference dialog that opens when you select Preferences from the application menu. This class contains all of the widgets that are in the user preference dialog. This class handles all of the preferences, stores them as attributes in the class, and persists them as member attributes to the database. It provides support for the tools to save and retrieve preferences for the user. This class defines all of the configurable properties that are used in Management Center. This is the base class that represents a user preference object. All preference objects in the Management Center should extend this class and implement the methods of this class.
wcfUserPreferencePanel wcfPreferenceManager
UserPreferencePanel.lzx PreferenceManager.lzx
wcfConfigProperties
ConfigProperties.lzx
wcfUserPreferenceObject
UserPreferenceObjects.lzx
Table 3-6 lists the services that the Preferences feature uses.
Table 3-6 Services used by Preferences Service GetPerson Interface com.ibm.commerce.memb er.facade.client.MemberFa cadeClient.findCurrentPer son() com.ibm.commerce.memb er.facade.client.MemberFa cadeClient.updatePerson( Map parameters) Description This service is called to retrieve the user preferences from the database.
ChangePerson
This service is called when you click OK in the Preferences dialog to save the preferences.
Table 3-7 on page 35 provides the various customization scenarios for user preferences.
34
Table 3-7 User preferences customization scenarios Scenario Change the layout of the preferences dialog Change the default values for preferences Required changes Change the order of the preference objects in wcfUserPreferencePanel (UserPreferencePanel.lzx. The default values are defined in wcfConfigProperties (ConfigProperties.lzx). Reference None
WebSphere Commerce v6 Information Center: Changing default values for settings in the Preferences dialog: http://publib.boulder.i bm.com/infocenter/wchel p/v6r0m0/topic/com.ibm. commerce.management-cen ter_customization.doc/t asks/ttfchangedefaultpr ef.htm
1. Create a new class that extends wcfUserPreferenceObject. 2. Insert the above new class to the list of existing preference objects in the dialog. 3. Create a new entry to the MBRATTR table in the database to register this new preference.
Refer to Chapter 10, Customizing user preferences on page 165 for an example.
Note: To add a new user preference, you must have at least Feature Pack 4 installed because wcfUserPreferenceObject class is declared private in previous versions.
35
Each tool consists of a number of assets that you must modify in order to customize a tool. In this section, we describe the various tool assets and the customization scenarios that can cause the assets to be modified.
Refer to Object definitions on page 37 for more information. Refer to Service definitions on page 41 for more information. Refer to Filter definitions on page 38 for more information. Refer to Search definitions on page 38 for more information.
36
Object definitions
An object definition class describes the characteristics of a Management Center object. The class defines the attributes, services, list definitions, property definitions, and client-side validators for an object. Each object definition should extend from wcfObjectDefinition and should be defined in its own OpenLaszlo source file. Table 3-9 lists possible customization scenarios for object definitions.
Table 3-9 Customization scenarios for object definitions Scenario Define a new object Required changes Define a new class that extends from wcfObjectDefinition. Update an object definition to include new user data or an attribute property. Reference Refer to Chapter 12, Custom Application Configuration Management tool on page 239 for an example. WebSphere Commerce v6 Information Center: Adding new fields in the Catalogs tool: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tut orial/ttfadpcat.htm Define a custom service or change an existing service Update an object definition to include a new wcfCustomService definition. Change the display name attribute of an object definition. Refer to Chapter 7, Adding a new service action on page 107 for an example.
WebSphere Commerce v6 Information Center: Changing the display name for a Management Center object: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tas ks/ttfchangedisplayname.htm
Update the object definition to include the new filter definition. Define a new client-side validator
Refer to Filter definitions on page 38 for more information. Refer to Chapter 6, Enabling the merchandising associations Semantic Attribute on page 89 for an example.
37
Search definitions
A search definition class describes the characteristics of an object search type that is available from the search widget. The class defines the search service, the view that displays the search results, and an optional class to define the advanced search options. Extend each search definition from wcfSearchDefinition, and define it in its own OpenLaszlo source file. Table 3-10 lists possible customization scenarios for search definitions.
Table 3-10 Customization scenarios for search definitions Scenario Define a new search type Required changes Define a new class that extends from wcfSearchDefinition to search for a custom business object. 1. Change the display name 2. Change the view that displays the search results Define a new advanced search class for a tool (for example, a custom tool) that does not support advanced search. Add new search conditions to an existing advanced search class. Reference Refer to Chapter 8, Advanced search for merchandising associations on page 119 and Chapter 12, Custom Application Configuration Management tool on page 239 for examples. None
Refer to Chapter 8, Advanced search for merchandising associations on page 119 for an example.
WebSphere Commerce v6 Information Center: Adding new search conditions in the advanced search of the Catalogs tool: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tut orial/ttfadsca.htm
Filter definitions
You can define a filter for an object, a list, or a business object editor. If you declare a filter as the child of an instance of wcfBusinessObjectEditor, the filter is selectable through a pull-down menu that is located at the top of the explorer view. If you declare the filter as a child of an instance of wcfObjectGrid, it is selectable through a pull-down menu that is located at the top of the list view.
38
Define each filter in its own OpenLaszlo source file and extend it from one of the following classes: wcfObjectTypeFilter It filters model objects based on an object type. You can define an object type filter for a business object editor, a primary object, or a list view of primary objects, for example, as a child of an instance of wcfBusinessObjectEditor, wcfPrimaryObjectDefinition, or wcfObjectGrid. Another example is in the Catalog tool, the catalog entry list view defines filters to filter products that are based on the product type: products only, bundles only, kits only, SKUs only, and all except SKUs. wcfObjectPathFilter It filters model objects based on the object path. You can define an object path filter only for a list view of primary objects, for example, as a child of a wcfOjbectGrid. wcfObjectPropertyFilter It filters model objects based on the value of an object property. You can filter an object property filter only for a list view of primary objects, for example, as a child of a wcfObjectGrid. Another example is in the Promotions tool, the promotion list view defines filters to filter the promotions based on the promotion group: order level promotions, product level promotions, and shipping promotions. Table 3-11 lists possible customization scenarios for filter definitions.
Table 3-11 Customization scenarios for filter definitions Scenario Define a new filter Required changes Define a new filter class that extends from one of the classes defined above. Reference Refer to Chapter 12, Custom Application Configuration Management tool on page 239 for an example.
39
WebSphere Commerce v6 Information Center: Changing a column name in a list view: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tas ks/ttfchangecolumnlist.htm
Update the list view definition to include the new filter definition.
40
Table 3-13 lists some customization scenarios for the properties view.
Table 3-13 Customization scenarios for properties view Scenario Define a new properties view Description Define a new class that extends from wcfObjectProperties for a custom business object. Update the properties view to include a new property. Create a new tab to include the new property. Reference Refer to Chapter 12, Custom Application Configuration Management tool on page 239 for an example. WebSphere Commerce v6 Information Center: Adding new fields in the Catalogs tool: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tut orial/ttfadpcat.htm Rearrange the contents of a properties view Move tabs, sections, or widgets around as desired. Refer to Chapter 9, Price comparison mashup on page 143 for an example.
Service definitions
The Management Center framework defines user interface services for actions, such as creating, updating, and deleting business objects. wcfService is the base class for all service types. Table 3-14 on page 42 lists customization scenarios for service definitions.
41
Table 3-14 Customization scenarios for service definitions Scenario Define a new create, update, delete, or refresh service for a custom object Required changes 1. Define a new class that extends from wcfCreateService, wcfUpdateService, wcfDeleteService, or wcfRefreshService. 2. Declare it as a child object of the object definition. 3. Define a controller JSP to implement the service. 1. Define a new class that extends from wcfInitService. 2. Declare it as a child of the business object editor instance of the new tool. 3. Define a controller JSP to implement the service. 1. Define a new class that extends from wcfSearchService. 2. Declare it as a child of the search definition. 3. Define a controller JSP to implement the service. Reference Refer to Chapter 12, Custom Application Configuration Management tool on page 239 for an example.
None
Refer to Chapter 8, Advanced search for merchandising associations on page 119 and Chapter 12, Custom Application Configuration Management tool on page 239 for examples. Refer to Chapter 7, Adding a new service action on page 107 for an example.
1. Define a new class that extends from wcfCustomService. 2. Declare it as a child of an object definition. 3. Define a controller JSP to implement the service. 1. Define a new class that extends from wcfGetChildrenService. 2. Declare it as a child of the object definition. 3. Define a controller JSP to implement the service. 1. Define a new class that extends from wcfGetReferencesService. 2. Declare it as a child of the primary object definition. 3. Define a controller JSP to implement the service.
Refer to Chapter 12, Custom Application Configuration Management tool on page 239 for an example. None
42
Widgets
Widgets are UI elements that display and edit Management Center object properties. The Management Center framework provides a set of high-level widgets that provide all of the behaviors that are required for authoring business objects. The main high-level widget that the framework provides is the Business Object Editor widget, which provides all of the behavior for a Management Center tool. You can use or extend any of the widgets that are part of the OpenLaszlo API, which are defined at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/index.jsp?topic= /com.ibm.commerce.management-center_customization.doc/refs/rtfmain.htm For information about how to create a new Management Center widget, refer to Creating a new Management Center widget at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.management-center_customization.doc/tasks/ttfcreatenewwidgets.ht m For information about how to use or extend a Management Center widget, refer to Using or extending Management Center widget at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.management-center_customization.doc/tasks/ttfuseexistingwidgets. htm Chapter 9, Price comparison mashup on page 143 provides an example in which you create a new widget that is similar in behavior to the grid widget (wcfObjectGrid) that the Management Center framework provides. Chapter 11, Spell Checker on page 179 provides an example in which you create a window dialog widget, which you can use to spell check a text property and make corrections.
43
about the Mediation layer architecture, refer to Section 2.2.3, The Mediation layer on page 20. The line-of-business Web application contains the following customizable assets: OpenLaszlo source code This is the source code for the Management Center user interface. Refer to 3.1.1, Management Center user interface on page 30 for details about customizing the OpenLaszlo code. Extension Struts configuration The file path is /WEB-INF/struts-extension.xml. This is the struts configuration file where you add your custom URL mappings. Get controller JSPs The Management Center sends a URL request to retrieve an object. The struts action forwards the request to a Get controller JSP that uses the wcf:getData tag to invoke the appropriate Get service to retrieve the list of Nouns from the WebSphere Commerce server. Serialization JSP fragments When returning a response, these JSP fragments transform the OAGIS Noun into the appropriate XML representation that the Management Center expects. These JSP fragments are included by the Get controller JSP pages. get-data-config.xml The Management Center framework uses the get-data-config.xml file to configure the wcf:getData tag that is defined in the Get controller JSPs. wc-componentname-clientobjects.xml The Management Center object definition files contain information that is used to map URL requests to BOD requests and vice versa. Table 3-15 lists some customization scenarios for the Management Center Web application.
Table 3-15 Customization scenarios for the Web application Scenario Define a new retrieval service for a Management Center object Required changes 1. Create a new Get controller JSP. 2. Create a new serialization JSP. 3. Add a new forward action to struts-extension.xml. Reference Refer to Chapter 12, Custom Application Configuration Management tool on page 239 for an example.
44
Required changes 1. Define the mapping between the Noun elements and the Management Center object in a new XML file (wc-componentname-c lientobjects.xml). 2. Add the XML file that you defined in the previous step to struts-extension.xml to the list of files that are specified in the config property of the BusinessObjectDocum entPlugin. 3. Create a new BusinessObjectDocum ent Struts action. The process to override an existing retrieval service is similar to creating a new one.
Reference Refer to Chapter 12, Custom Application Configuration Management tool on page 239 for an example.
WebSphere Commerce v6 Information Center: Adding new search conditions in the advanced search of the Catalogs tool: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tut orial/ttfadsca3.htm
Override a process service for an existing Management Center object Override an existing error message
The process to override an existing process service is similar to creating a new one.
None
WebSphere Commerce v6 Information Center: Mapping of validation error reason codes: http://publib.boulder.ibm.com/infocente r/wchelp/v6r0m0/topic/com.ibm.commerce. management-center_customization.doc/tas ks/ttfmaperror.htm
45
46
47
48
Chapter 4.
49
50
Solution Startup
Release
Solution Outline
Macro Design
Micro Design
Build Cycle
Deployment
Solution Close
51
The project life cycle consists of the following phases: Solution Startup Based on high-level business requirements, you investigate possible solutions, for example, if your company, needs to increase sales, the solution could be to introduce a new sales channel that reaches new customers. The decision might then be an online shop. This phase is not specific to Management Center customization; therefore we do not discuss it here. Solution Outline Based on specific business requirements, you build a global solution design and choose technology and products. In this phase, you define the main building blocks on a coarse-grained level, such as software packages, hardware, and networks. On a system level, you clarify the context and discover the main interfaces and use cases. You also elaborate on cost estimation, schedule, and risk assessment. An important outcome of this phase is your decision to use Management Center. In 4.5, Solution Outline phase on page 53, we describe the steps and work items that are in the Solution Outline phase. Macro Design The purpose of the Macro Design phase is for you to develop a robust-architectural framework on which to build agile releases. In this phase, you begin Management Center customization work. You fulfill several planning and high-level design tasks. The IT architect and lead developer are involved and are supported by the project manager. In 4.6, Macro Design phase on page 56, we describe the steps and work items that you perform in the Macro Design phase. Micro Design In this phase, you prepare for the build cycle of a specific release by driving the architecture and design to a release-specific and implementation-platform view. The lead developer ensures that the developers understand the design they are considering and that the design is feasible to implement. IT architect supports and supervises. In 4.7, Micro Design phase on page 59, we describe the steps and work items that you perform in the Micro Design phase.
52
Build Cycle In this phase, developers develop and test the system incrementally until the objectives of the release are achieved. In 4.8, Build Cycle phase on page 61 we discuss the steps and work items that you need to take care of during the build cycle. Deployment In this phase, the deployment manager performs the last phase of the release cycle. After the system is deployed successfully, the project team prepares for the next release. In 4.9, Deployment phase on page 63, we cover the steps and work items regarding customizing the Management Center in the deployment phase. Solution Close The Solution Close phase covers the required close activities, which includes gathering metrics and harvesting reusable assets. The Solution Close phase brackets the end of the engagement. The Solution Close phase is not specific to Management Center customization; therefore, we do not discuss it here. Note: You conduct Solution Outline and Macro Design one time. The three phases that make up a release are grouped together and are performed many times, one time for each release of the system. For details about each phase, we suggest that you refer to the IBM Redbooks publication, Planning and Managing the Deployment of WebSphere Commerce, SG24-7588, which is available at: http://www.redbooks.ibm.com/abstracts/sg247588.html?Open
53
Server-side services
Invocating WebSphere Commerce services and building XML response documents is replaced by static XML response documents. The Management
54
Center Web application is the Mediation layer where you can place static data in the JSP files that create the response.
55
customize an existing Management Center tool to meet a requirement that you decide is a partial fit or maybe a gap. New tools that you can create The Management Center framework is exposed to allow developers to create a completely new tool. You can create a new tool to meet requirements that you decide are gaps. For all items, determine their default behaviors, capabilities, and limitations. Examine all of the available information sources in the following resources: Chapter 1, IBM Management Center for WebSphere Commerce on page 3 Chapter 2, Overview of the IBM Management Center for WebSphere Commerce environment on page 15 Chapter 3, Customization overview on page 29 The WebSphere Commerce Information Center, which is at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/index.jsp
56
57
Management Center file and class naming conventions http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/concepts/ctffilenaming .htm
Prepare to test the functionality and do some non-functional testing. Testing for non-functional requirements includes usability testing and performance testing.
Functional testing
For functional testing, there are three general stages to test: OpenLaszlo client application As of today, most of the GUI test automation tools cannot test OpenLaszlo applications. OpenLaszlo does not expose its run time through an interface to external programs, such as a remote debugger or other remote agents; therefore, testing is only possible from inside the run time. LzUnit provides a unit test framework for OpenLaszlo. Similar to JUnit, LzUnit allows you to test units of code, but it is focussed on testing Laszlo classes and methods. Both test code and tested code are running inside the OpenLaszlo runtime. Refer to OpenLaszlo documentation to learn about LzUnit: http://www.openlaszlo.org Management Center Web application The communication between the client and the Web application uses the HTTP Post method. The request object is a URL with name-value-pair parameters, and the response object is an XML document. You can use one of the numerous Web test frameworks that are available to simulate an OpenLaszlo client request.
58
WebSphere Commerce Services The new BOD command framework uses the client library as a gateway for unit tests. When you create new components, the Design Pattern Toolkit (DPTK) generates a test unit project that contains some basic unit tests. You can use these JUnit-based tests as a starting point for your own set of tests. However, if your custom services are used only in the Management Center, you might find it adequate to test them only through the Management Center Web application.
Performance testing
The performance testing strategy for Management Center needs to be different from the performance testing strategy of a regular Web 1.0 application. You can not think of the response time in the traditional terms of time taken between a single request and response. In the case of Management Center, when you click on something, the request might initiate a bunch of calls in the background, which might return responses asynchronously. So although we have an identified starting point, its difficult to see all of the end points. Note: OpenLaszlo has a profiler that measures the time spent in each function. However, at the time of writing this book, this profiler is unsupported and does not work reliably on different operating system (OS) platforms. For more information, refer to: http://wiki.openlaszlo.org/CallProfiler Management Center is a search-intensive application. You can tune the database to improve the performance of the search queries. For details, refer to: Management Center search tables at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.management-center.doc/refs/rpmmctables.htm Improving performance of case-insensitive searches in the Management Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.management-center.doc/refs/rtforacleperform.htm
59
release. The Solution Outline and Macro Design phases take a release-independent view, whereas Micro Design drills down and focuses on a specific release. The Micro Design focuses on laying out a complete coding framework, which includes all of the Management Center-specific assets, such as user interface definitions, user interface services, and component services that you need to develop. Create an object model or a class diagram to illustrate the relationship between different classes.
60
For more information about the assets that you typically create or modify as you customize the Management Center Web application, refer to 3.1.2, Management Center Web application on page 43.
61
You can run multiple programming cycles serially, in parallel, or in combination. Often, separate development teams focus on a sequence of related programming cycles. These individual development teams work in parallel and independently from each other. For Management Center customization, we recommend that a two-to-three week effort involves a small team of one-to-four developers for a single programming cycle. Larger projects that need customization in all areas (OpenLaszlo client, Management Center Web application and WebSphere Commerce services) might require a longer development cycle. A team can work on a complete scenario and make changes to all areas in a cycle, which we call vertical programming. This approach is followed in most of the projects. Alternately, a team works only on a specific area (for example, the user interface) but on multiple scenarios during a cycle taking benefit from highly skilled experts (for example, developers skilled at OpenLaszlo and the Management Center framework). This approach is called horizontal programming. With Management Center customization, both approaches are feasible and it is up to you to decide which way to go. The Management Center architecture allows decoupled development through interface mock-ups, decoupled server and client components, and the support for unit testing.
62
63
64
Customization Area
Database Schema
BOD Commands
Customer UI
Tools UI
Management Center UI
Sales Center UI
Storefront
Accelerator
Loading Utilities
Figure 4-2 Matrix of typical WebSphere Commerce sub-projects and customization items
Review the following example: Storefront customization typically requires that you customize the database schema, EJB Persistence layer, controller and task commands, and the customer user interface. If storefront implementation uses data that is not being stored in the default WebSphere Commerce database schema, extend the schema. If line-of-business users might manage this data, you must extend one of the tools. In this case, you must customize either Management Center or Accelerator, depending on the special requirement and the tools capabilities. Let us assume that the choice is Management Center. Now you discovered that there might be an interdependency between your Management Center customization sub-project and your storefront customization sub-project, by way of database schema customization.
65
66
Chapter 5.
67
68
WebContent/WEB-INF/src/lzx/commerce/<component> <component>ManagementToolDefinition.lzx Tool definition file contains: Context value definitions Help link definition Instantiation for: Init service Primary business objects Search definitions Filter definitions Objects from other components that this tool uses <component>ExtensionsLibrary.lzx Add entries in this file to include any OpenLaszlo files created for customization. WebContent/WEB-INF/src/lzx/ commerce/<component>/restricted Some components might have the following sub-directories under this restricted directory: listViewDefinitions objectDefinitions propertiesViews searchDefinitions Note: Do not customize files in restricted folders. They are replaced when you apply fix packs or migrate to a new release. <component>FilterDefinitions.lzx Filter definitions for component. <component>Library.lzx Library file with inclusions for all OpenLaszlo files that the component uses. <component>ResourceBundle.lzx Defines the resource bundle class and property keys that the component uses. <component>Resources.lzx Defines image resources that the component uses.
WebContent/WEB-INF/src/lzx /commerce/<component>/listViewDefinitions
69
Directorya
Files in this directory Files under this directory contain list and grid classes that the component uses.
WebContent/WEB-INF/src/lzx /commerce/<component>/objectDefinitions Files under this directory contain primary, organizational, child, reference and common object definitions that the component uses. WebContent/WEB-INF/src/lzx /commerce/<component>/propertiesViews Files under this directory contain property view definitions that are used to create and edit objects. WebContent/WEB-INF/src/lzx /commerce/<component>/searchDefinitions Files under this directory contain search definitions that the component uses. src/com.ibm.commerce.<component>.client.lobtools.properties Note: When files are built, resource bundles are copied under WebContent/WEB-INF/classes/ directory This package contains resource bundles for all user interface text, labels, and messages for component.
WebContent/jsp/commerce/<component> JSP files that the Management Center Web application uses to mediate requests that are made between the client and the WebSphere Commerce services. a. All directory paths are relative to LOBTools directory.
70
Regardless of your WebSphere Application Server topology, the deployment tools work in the same way. Therefore, if you are working with a clustered environment, the process to deploy your extensions is the same if you were not working in a clustered environment. If your extension is only touching the Management Center, which is stored under the LOBTools project, you can deploy your changes as a single WAR module file update. If you also made some other changes, for instance, extended the object model or the Data Service layer, then you must deploy those as usual as a Partial Application update.
71
5.2.3 Deploying the WAR module to the target WebSphere Commerce server
At this point, we have a new Management Center WAR module to deploy, and the current application is backed up for safety. We are ready to deploy the changes. You can deploy with the Web browser using the WebSphere Administrative Console or using the scripting tools from the command line. Both options are well documented in the Info center. The command line wsadmin tool currently supports both JACL and Jython type scripting. Visit the following Web site, and follow the steps for deploying a WAR module: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.developer.doc/tasks/tdpdeploying_war_assets_entire.htm Note: When you specify the application update options, the relative path to your module file is simply LOBTools.war, as shown in Figure 5-2 on page 73. The directory is located directly below the instance EAR path, so you can usually find it under here:
WAS_profiledir/installedApps/WC_instance_name_cell/WC_instance_name.ear/
72
73
started successfully, log out of any previous Management Center client sessions that you might have running, and then close the related browser Logon window. You can refresh the Management Center launch page (the one with the URL line), by pressing F5, to start a new session. Log back in and you should be able to verify the changes.
5.3 Debugging
OpenLaszlo proThankvides a powerful debugger hat can detect runtime errors, display debug information at many different detail levels, and inspect objects. In a default WebSphere Commerce Developer installation, the Debugger window, shown in Figure 5-3, is displayed when you launch the Management Center for the first time.
In Figure 5-3, the three small buttons at the top right corner are Clear, Minimize and Close. The top portion of the window is the debugger log and this is where the debug messages appear. Use Debug.write() statements in your code to print to the debugger log. The method is variadic, which means that it takes any number of arguments. You can inspect class instances directly from the console: 1. Enter the class name and press Enter. The instance comes back in blue and it is an object that you can click. 2. You can inspect the structure by clicking the blue elements. You enable the Debug mode from the Laszlo compile process settings. Right-click LOBTools, and click Properties OpenLaszlo Settings, as shown in Figure 5-4 on page 75.
74
When you change the debug mode, the settings become effective only after you rebuild the LOBTools project because the settings that you define here are Laszlo compiler settings, and the compiler is invoked as part of the build process.
The client-side Logging and Tracing settings control the information that is displayed and the detail level of the information. You can specify these settings in the Management Center URL or dynamically from the Logging and Tracing menu after Management Center is running. To see the menu, launch the Management Center with the logger.display=true parameter. All of the target options might not appear unless you initially specify logger.target=debugger in the URL.
75
When we developed the samples in this Redbooks publication, we typically launched the Management Center with a URL similar to the following example, which enables the Logging and Tracing menu, shown in Figure 5-5, and specifies the Debugger window as the target. We added the host name jserver.com as an alias to localhost in the local HOSTS file so that the Web browser does not bother you with the SSL certificate mismatch messages any longer. https://jserver.com:8000/lobtools/cmc/ManagementCenter?logger.display= true&logger.target=debugger There is also a developmentMode=true setting, which causes XML and properties files to be reloaded if their timestamps change, which saves the time to restart the server. We found this of relatively little value because you still need to rebuild the LOBTools project and start a new browser session, and usually there were not that many changes to the properties files anyway. See the Information Center for setting the Logging and Tracing details: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.management-center.doc/tasks/ttflogtrace.htm
76
Another nice bonus that comes from launching the Management Center with the Tracing and Logging parameters is that now the browser window shows the fully qualified server host name in the browser window title, as shown in Figure 5-6, which is helpful when you work with multiple WebSphere Commerce servers (test, development, and so on.) simultaneously.
<method name="updateText" args="txt"> <![CDATA[ if (wcfLogger.enabled) { wcfLogger.entering("com.redbooks.components", "extPropertyRTEwithSpellCheck", "updateText", [txt]); wcfLogger.log("com.redbooks.components", "INFO", "extPropertyRTEwithSpellCheck", "updateText", "updating text editor " + txt); } < your code > if (wcfLogger.enabled) { wcfLogger.exiting("com.redbooks.components", "extPropertyRTEwithSpellCheck", "updateText");
Note the wcfLogger.log call, which you can use to print additional details on any object or property values.
77
To see your trace: 1. Launch Management Center with the matching additional parameter: &logger.components=com.redbooks.components, as shown in Figure 5-7. 2. Set the detail levels as you want. You can set the default from the URL line, but unfortunately not for each component individually.
78
79
80
There are a few things to note regarding CVS and OpenLaszlo. Make sure that you specify that the .lzx, .olsettings and .tpl file extensions refer to ASCII type files. The CVS default is binary. Although the binary default might still work, using ASCII makes better sense with these files: 1. Enable CVS support in WebSphere Commerce Developer. Click Window Preferences Workbench Capabilities. 2. Expand the Team capability, select Core Team Support and CVS Support, and click OK. 3. Click Window Preferences again. The Team menu should now appear in the navigation bar. 4. Click Team CVS File Content. Add lzx, olsettings, and tpl as the new ASCII type file extensions, and click OK. We used the compatibility settings in Figure 5-8 on the CVS Server panel.
After you install the software, all of the developers will already have the LOBTools project in their workspace. Importing this as a new project or replacing it completely is not a good idea with this type of pre-loaded project. What seemed to work well though was running Share Project after installation from every workstation, and then synchronizing changes when needed using the Synchronize with Repository option. The Team Synchronizing perspective can show both the incoming and outgoing changes clearly, as shown in Figure 5-9 on page 82. The developer always has control over what changes are brought in from the repository, and what changes are committed back to the repository. You can check the changes one-by-one. Alternately, you can copy all changes in using one click.
81
Figure 5-9 Bringing in the latest changes from the team repository
The only minor issue we encountered was that the Synchronize view also listed some files that were identical in content and only had a different time stamp. We went through all of these files with the Compare Editor to ensure that there were no other differences.
82
83
3. At the end of the file, comment out the tools that you are not presently using. Figure 5-10 is an example of running with the Catalog tool only.
Figure 5-10 Commenting out tools that are not being extended
4. Rebuild the project. Remember to uncomment this when you are done.
84
Refresh (click the F5 key) the Management Center launch Web page, which is https://jserver:8000/lobtools/cmc/ManagementCenter You will have a new Management Center session going on right there. If you made changes to the Struts configuration or to the properties files, you must restart the server.
85
86
Part 3
Part
87
88
Chapter 6.
what type of accessory we are dealing with, which we can use on the storefront to
group the compatible accessories together under meaningful accessory headings accordingly.
89
To learn more about merchandising associations and the semantic specifiers, see the Info Center documentation: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.user.doc/concepts/cpnmamass.htm In this scenario, you enable the semantic specifier, and then define two compatible accessories for a master cylinder in the AdvancedB2BDirect catalog. Ideally the brake pads and tires would be mapped to a car, but this sample catalog does not come with such products, so we simply used the already existing racing master cylinder. The same idea still applies, and this works just as well for our illustration purposes. The result will appear similar to Figure 6-1.
90
The MASSOC table that holds the semantic specifiers is documented in the Information Center: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.database.doc/database/massoc.htm
91
under the Merchandising Associations tab, which is located next to the Association column. We know that the WebSphere Commerce schema already includes the semantic attribute, and the semantic attribute specifiers are persisted in the MASSOC table. The next task is to determine if the semantic specifier is already provided in the object model: 1. Open WebSphere Commerce Developer, and navigate to LOBTools WebContent WEB-INF config com.ibm.commerce.catalog. 2. Open the wc-catalog-client-objects.xml file, and search for semantic. We can see that the merchandising association definition already contains a parameter named semantic: <_config:URLParameter name="semantic" nounElement="/Association/@semantic" /> This is what we will refer to from the merchandising association grid.
92
Note: You decide the naming structure here depending on your needs. For larger multi-language extensions, you could define tool specific packages following the same naming convention that is used within WebSphere Commerce Developer itself, for example, the com.ibm.commerce.catalog.client.lobtools.properties package holds all properties files for the Catalog tool, and you could set up a similar structure for your larger extension projects too. For the relatively small set of extensions in this book, we decided to use a single package and hold all properties under there. Right-click the com.redbooks.commerce.client.lobtools.properties package, and click New Other Simple File. 4. In the File name field, enter CatalogLOB.properties. Use this general file name without the locale specification when there is no match for a locale-specific version. In your project, you might need to provide language-specific texts, and then add the locale to the properties filename like this: <basename>_<locale>.properties. Explore the other existing packages, and note the use of the en_US locale in there as an example. 5. Click Finish. The CatalogLOB.properties file opens. 6. Define the following properties: merchandisingAssociationSemantic_ColumnHeader=Semantic merchandisingAssociationSemantic_REQUIRES=REQUIRES merchandisingAssociationSemantic_COMES_WITH=COMES_WITH merchandisingAssociationSemantic_TEMP=TEMP merchandisingAssociationSemantic_NONE=NONE merchandisingAssociationSemantic_BRAKE_PAD=BRAKE_PAD merchandisingAssociationSemantic_TIRE=TIRE merchandisingAssociationSemanticValidationWarning=This semantic applies to ACCESSORY type merchandising associations only. The last two semantic entries are the additional semantics for the AdvancedB2BDirect store catalog with the intent of identifying compatible brake pads and tires as an accessory. Although in this implementation all of your semantics need to be listed in the properties file, the mechanism is quite flexible, and you can use it for many different kinds of merchandising relationships that can be imagined between the source and target catalog entry.
93
Another more advanced approach is to pull this information directly from the MASSOC table: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.database.doc/database/massoc.htm
<library> <class name="extCatalogResourceBundle" extends="wcfResourceBundle" baseName="com.redbooks.commerce.client.lobtools. properties.CatalogLOB"> <!-- Merchandising Association Semantic properties --> <wcfResourceBundleKey name="merchandisingAssociationSemantic_ColumnHeader" /> <wcfResourceBundleKey name="merchandisingAssociationSemantic_REQUIRES" /> <wcfResourceBundleKey name="merchandisingAssociationSemantic_COMES_WITH" />
94
<wcfResourceBundleKey name="merchandisingAssociationSemantic_TEMP" /> <wcfResourceBundleKey name="merchandisingAssociationSemantic_NONE" /> <wcfResourceBundleKey name="merchandisingAssociationSemantic_BRAKE_PAD" /> <wcfResourceBundleKey name="merchandisingAssociationSemantic_TIRE" /> <!-- Merchandising Association Semantic Validator Warning --> <wcfResourceBundleKey name="merchandisingAssociationSemanticValidationWarning"/> </class> <extCatalogResourceBundle id="extCatalogResources"/> </library> 8. Save and close the file.
95
<include href="../../redbooks/catalog/ extCatalogManagementResourceBundle.lzx" /> <!-- Merchandising Semantic Validator --> <include href="../../redbooks/catalog/objectDefinitions/ MerchandisingSemanticValidator.lzx" /> </library>
Note: We also included another extension file here, MerchandisingSemanticValidator.lzx, which will be our validation class for checking correct user input. We discuss this further in the next section.
4. Click Save. For more details about working with the library class files, see this Information Center page: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.management-center_customization.doc/tasks/ttfaddlibrary.htm
96
4. Add the following definition between the name and quantity definitions: <wcfGridComboBox propertyName="semantic" required="false" editable="true" text="${extCatalogResources. merchandisingAssociationSemantic_ColumnHeader.string}" width="120"/> 5. Save and close the file. 6. Add the property definition to the catMerchandisingAssociation class. In Package Explorer, navigate to LOBTools WebContent WEB-INF src lzx commerce catalog objectDefinitions. 7. Open the CatalogCommonObjectDefinitions.lzx file. 8. Locate the catMerchandisingAssociation class, and add the code in Example 6-2 to it before the quantity definition.
Example 6-2 Adding the semantic specifier property to the object class definition
<class name="catMerchandisingAssociation" ... <!-- This property definition represents the options for the semantic specifier available between two catalog entries. --> <wcfPropertyDefinition propertyName="semantic" required="false" displayName="${extCatalogResources. merchandisingAssociationSemantic_ColumnHeader.string}"> <wcfPropertyValue displayName="${extCatalogResources. merchandisingAssociationSemantic_REQUIRES.string}" value="REQUIRES"/> <wcfPropertyValue displayName="${extCatalogResources. merchandisingAssociationSemantic_COMES_WITH.string}" value="COMES_WITH"/> <wcfPropertyValue displayName="${extCatalogResources. merchandisingAssociationSemantic_TEMP.string}" value="TEMP"/> <wcfPropertyValue displayName="${extCatalogResources. merchandisingAssociationSemantic_NONE.string}" value="NONE"/> <wcfPropertyValue displayName="${extCatalogResources. merchandisingAssociationSemantic_BRAKE_PAD.string}" value="BRAKE_PAD"/>
97
displayName="${extCatalogResources. merchandisingAssociationSemantic_TIRE.string}" value="TIRE"/> </wcfPropertyDefinition> 9. A few lines below this, add a default value for the Display Sequence, which corrects a tiny defect in the FEP 3.0.1 code: <!-- By default, quantity is set to 1, and sequence is set to 0 --> <dataset name="template"> <quantity>1</quantity> <sequence>0</sequence> </dataset> 10.While you edit the Merchandising Association class, you can also include a reference to our future validator class here. Right before the class definition closure, include the following line (in bold): <extMerchandisingSemanticValidator errorMessage="${extCatalogResources. merchandisingAssociationSemanticValidationWarning.string}" /> </class> 11.Save and close the file. 12.Build your project. This action invokes the Laszlo compiler and eventually generates a new copy of the ManagementCenter.swf file for you.
98
To create the following new sub-folder, and put our custom Validator class in the new sub-folder: /LOBTools/WebContent/WEB-INF/src/lzx/redbooks/catalog/objectDefinitions 1. In the Package Explorer, navigate to LOBTools WebContent WEB-INF src lzx redbooks catalog. 2. Create a new sub-folder, and name it objectDefinitions. 3. Right-click objectDefinitions, select New Other Simple File, and click Next. 4. In the File name field, enter MerchandisingSemanticValidator.lzx. 5. Click Finish. The MerchandisingSemanticValidator.lzx file opens. 6. Enter the code in Example 6-3 or, import the code from the provided solution package.
Example 6-3 Validator for checking the semantic assignment
<library> <class name="extMerchandisingSemanticValidator" extends="wcfValidator"> <attribute name="errorMessage" value="" type="string"/> <method name="validate" args="o"><![CDATA[ if(this.validateMerchandisingSemantic(o)){ o.clearValidationError(this); }else{ o.addValidationError(this, this.errorMessage); } ]]> </method> <method name="validateMerchandisingSemantic" args="o"><![CDATA[ var semantic = o.getProperty("semantic").value; var name = o.getProperty("name").value; var accessory = catalogResources ["merchandisingAssociationName_ACCESSORY"].string; var brake_pad = extCatalogResources ["merchandisingAssociationSemantic_BRAKE_PAD"].string; var tire = extCatalogResources ["merchandisingAssociationSemantic_TIRE"].string if (semantic == brake_pad && name != accessory) return false;
99
else if (semantic == tire && name != accessory) return false; else return true; ]]> </method> </class> </library> 7. Click Save. The logic is that if the Brake Pad or Tire semantic are selected, but the selected association type is not accessory, the validation method returns false, and we return the errorMessage. In all other cases the validation returns true.
100
<assocId>${association1.uniqueID}</assocId> <name><![CDATA[${association1.name}]]></name> <originalName><![CDATA[${association1.name}]]></originalName> <semantic><![CDATA[${association1.semantic}]]></semantic> ... 8. Save the file. The next step is to update the Struts configuration so that the new version will be used. 9. In the Package Explorer, navigate to LOBTools/WebContent/WEB-INF/. 10.Open the struts-extension.xmlfile. There are actually four different action definitions for retrieving the association data for product, SKU, dynamic kit, and bundle respectively. You can forward them all to the same generic JSP. Insert the definitions as follows: <!-- Action Mappings --> <action-mappings> <action path="/GetProductChildren-MerchandisingAssociations" forward= "/jsp/redbooks/catalog/GetMerchandisingAssociations.jsp" /> <action path="/GetSKUChildren-MerchandisingAssociation" forward= "/jsp/redbooks/catalog/GetMerchandisingAssociations.jsp" /> <action path="/GetKitChildren-MerchandisingAssociations" forward= "/jsp/redbooks/catalog/GetMerchandisingAssociations.jsp" /> <action path="/GetBundleChildren-MerchandisingAssociation" forward= "/jsp/redbooks/catalog/GetMerchandisingAssociations.jsp" /> </action-mappings> 11.Save the file.
101
To insert the new semantics into the commerce database: 1. Open the database access URL: https://localhost:8002/webapp/wcs/admin/servlet/db.jsp 2. List the currently defined semantics by entering the following SQL: SELECT * FROM MASSOC; You should see the four predefined semantics listed in this table. For the two new semantics, enter the following two SQL statements: INSERT INTO MASSOC VALUES (BRAKE_PAD, Brake pad semantic, NULL, NULL); INSERT INTO MASSOC VALUES (TIRE, Tire semantic, NULL, NULL); 3. Click Submit Query. While you access the database, you might also want to explore the contents of the two other tables that deal with merchandising associations. They are MASSOCTYPE and MASSOCCECE. The MASSOCCECE table holds the actual associations for each source and target catalog entry pair.
102
6. Set the association type to ACCESSORY for both, and set the semantic to BRAKE_PAD and TIRE, accordingly. Increase the quantity to four. 7. Click Save. There should be no error messages. Click Reload. The same information should still appear. This is a good test to see that your JSP is rendering correctly. 8. Try assigning the BRAKE_PAD or TIRE semantic for some other association type, such as XSELL or UPSELL. Error messages should appear. Using the database access URL, you might also want to verify that the new associations were stored in the MASSOCCECE table. Note: You can see that the RANK column is not populated and receives a NULL value. A further extension task is to add a new entry field for this in the Management Center UI, right after the semantic specifier. We will leave this to you as an extra challenge to try out, following the same ideas that we presented in this chapter. 9. Login to the AdvancedB2BDirect storefront, and navigate to the Racing Master Cylinder. 10.Verify that the accessory recommendation is made on the product page, as shown in Figure 6-3 on page 104.
103
Having defined the semantics, the next customizing task for this storefront is to create meaningful groups for the various recommendations. On the product page, you could now list the compatible brake pads under a Brake Pads heading, tires under Tires, and so on. This is a JSP customizing task that is outside of the scope of this chapter. Another more sophisticated version could use data services and retrieve the possible values from the MASSOC table. You can use the same approach for the association types too, based on the MASSOCTYPE table, which would require more work, but it could be a better option if there are many different types that continuously change.
104
For further study, see the MASSOC and MASSOCTYPE table definitions here: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.database.doc/database/massoc.htm http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.database.doc/database/massoctype.htm This extension touched some of the basic extension points of modifying a view definition, providing additional properties, writing a validator class, and following best practices for extending the JSP files.
105
106
Chapter 7.
107
108
http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.openlazlo.doc/lzx/commerce/catalog/objectDefinitions/ProductPrim aryObjectDefinition.lzx/catProductPrimaryObjectDefinition.html To open the file: 1. In the Package Explorer, navigate to LOBTools WebContent WEB-INF src lzx commerce catalog objectDefinitions. 2. Open the ProductPrimaryObjectDefinition.lzx file. There are many services that are already defined. We will add our two new services at the end. Locate the definition for wcfDeleteService, and insert the definitions in Figure 7-1 on page 116 after it:
Example 7-1 Adding a new custom service action
<!--- Make the selected products buyable --> <wcfCustomService url="/cmc/MakeBuyable" displayName="${extCatalogResources.makeBuyable.string}" toolbarIcon="activateToolbarIcon"> <wcfEnablementCondition conditionId="not_buyable" propertyName="xprop_buyable" enablementValue="0" objectPath="CatalogEntryExtraProperties" /> <wcfServiceParam name="storeId"/> <wcfServiceParam name="catentryId" propertyName="catentryId" /> <wcfServiceParam name="extraPropertiesId" propertyName="extraPropertiesId" objectPath="CatalogEntryExtraProperties" /> <wcfServiceParam name="xprop_buyable" value="1"/> </wcfCustomService> <!--- Make the selected products not buyable --> <wcfCustomService url="/cmc/MakeNotBuyable" displayName="${extCatalogResources.makeNotBuyable.string}" toolbarIcon="deactivateToolbarIcon"> <wcfEnablementCondition conditionId="buyable" propertyName="xprop_buyable" enablementValue="1" objectPath="CatalogEntryExtraProperties" /> <wcfServiceParam name="storeId"/> <wcfServiceParam name="catentryId" propertyName="catentryId" /> <wcfServiceParam name="extraPropertiesId" propertyName="extraPropertiesId" objectPath="CatalogEntryExtraProperties" /> <wcfServiceParam name="xprop_buyable" value="0"/> </wcfCustomService>
109
Let us discuss the definition in Example 7-1 on page 109 for a moment. Note the use of the wcfCustomService API class here. This class is the entry point for mapping new actions to the Management Service UI. See the Information Center documentation for this class here: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.openlazlo.doc/lzx/commerce/foundation/restricted/CustomSer vice.lzx/wcfCustomService.html This definition maps the two new services to Struts actions. We will add those actions to our extended Struts configuration soon. We are referring to the previously defined resource bundle for the menu text and the link to the menu icon is in place. The icon resources are actually defined in the .../foundation/restricted/Resources.lzx file. We are not extending this restricted file, but only referring to an existing icon resource in there. Alternately, you can copy the resource files to your own resource folder. For this simple scenario, we just link to the existing resources, which happens to be quite suitable for our purposes. If you want to explore what other icons are available in the tool, navigate to the .../foundation/restricted/resources/normal folder (or other subfolders there), and open the Thumbnails view in WebSphere Commerce Developer. Another important point in this definition is to identify the actual buyable property. The Information Center contains the buyable information that is available in the xprop_buyable property, but that is part of the extended properties definition and is accessible behind another object path. See the Catalog Entry browse grid definition in the Information Center: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.openlazlo.doc/lzx/commerce/catalog/listViewDefinitions/Cat alogEntryGrid.lzx/catCatalogEntryBrowseGrid.html To address that property, we need to pass the extraPropertiesId in the service request as well. Since no additional user input is required, passing the new value is really simple. One last point about this definition, the wcfEnablementCondition, which declares the condition when the service will be available to user. This is a useful feature in this particular instance and using it makes the UI appear less cluttered also. 3. Close and save the file.
110
<class extends="wcfObjectGrid" ... ... ... <wcfGridPropertyImage name="xprop_buyable" objectPath="CatalogEntry/CatalogEntryExtraProperties" propertyName="xprop_buyable" text="${catalogResources.productBuyable_ColumnHeader.string}" editable="false" enableFilter="true" imageResourceMap="${{0: 'nonBuyableIcon', 1: 'buyableIcon'}}"> visible="true" width="70" </wcfGridPropertyImage> <!-<wcfGridCheckbox trueValue="1" falseValue="0" name="xprop_buyable" objectPath="CatalogEntry/CatalogEntryExtraProperties" propertyName="xprop_buyable" text="${catalogResources.productBuyable_ColumnHeader.string}" visible="true" width="90" alignment="center"/> -->
111
<class name="catCatalogEntryExtraProperties" extends="wcfChildObjectDefinition" ... ... </wcfPropertyDefinition> <!-- Buyable flag --> <wcfPropertyDefinition propertyName="xprop_buyable" displayName="xprop_buyable"> <wcfPropertyValue displayName="${extCatalogResources.notBuyable.string}" value="0"/> <wcfPropertyValue displayName="${extCatalogResources.buyable.string}" value="1"/> </wcfPropertyDefinition> </class> As you can see, for most of the properties a definition is already provided, and you can further customize it. However, in the case of xprop_buyable, by default there is no definition provided. Fortunately, adding one of our own is still effective, which you will see in the testing phase soon.
112
<!-- Make buyable / not buyable --> <action path="/MakeBuyable" parameter="CatalogEntry" type="com.ibm.commerce.foundation.client.facade.bod.servlet.struts. BusinessObjectDocumentAction" className="com.ibm.commerce.foundation.client.facade.bod.servlet. struts.BusinessObjectDocumentActionMapping"> <set-property property="contextParameters" value="storeId,langId" /> <set-property property="verb" value="Change" /> <set-property property="documentRootFactory" value="com.ibm.commerce.catalog.facade.datatypes.CatalogFactory" /> <set-property property="clientLibrary" value="com.ibm.commerce.catalog.facade.client. CatalogFacadeClient" /> <set-property property="clientLibraryMethod" value="changeCatalogEntry" /> <set-property property="actionCode" value="Change"/> <forward name="success" path="/jsp/redbooks/catalog/RespondMakeBuyable.jsp"/> </action> <action path="/MakeNotBuyable" parameter="CatalogEntry" type="com.ibm.commerce.foundation.client.facade.bod.servlet.struts. BusinessObjectDocumentAction" className="com.ibm.commerce.foundation.client.facade.bod.servlet. struts.BusinessObjectDocumentActionMapping"> <set-property property="contextParameters" value="storeId,langId" /> <set-property property="verb" value="Change" /> <set-property property="documentRootFactory" value="com.ibm.commerce.catalog.facade.datatypes.CatalogFactory" />
113
<set-property property="clientLibrary" value="com.ibm.commerce.catalog.facade.client. CatalogFacadeClient" /> <set-property property="clientLibraryMethod" value="changeCatalogEntry" /> <set-property property="actionCode" value="Change"/> <forward name="success" path="/jsp/redbooks/catalog/RespondMakeNotBuyable.jsp"/> </action> The mappings in Example 7-4 on page 113 are naturally very similar. They are modeled after the UpdateCatalogEntryExtraProperties action in the original Struts configuration, which is stored in the struts-ibm-tools.xmlfile. For more details about transforming the URL request to a BOD service, see the Information Center page here: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.management-center_customization.doc/refs/rtfprocessservices.htm In this case, we are simply mapping our new action to an existing Change BOD service class. This class is responsible for performing the requested xprop_buyable flag update action on the server side. A possible more advanced extension can include writing your own custom service module as well, for example, a custom BOD Process service could additionally make the child catalog entries (SKUs) buyable.
114
<catentryId>${param.catentryId}</catentryId> <object objectType="CatalogEntryExtraProperties"> <extraPropertiesId>${param.extraPropertiesId} </extraPropertiesId> <xprop_buyable>1</xprop_buyable> </object> </object> This JSP will return an <object> document that refers to the subject catalog entry ID and the new buyable value. 4. Save and close the file. 5. Create the other response JSP file, RespondMakeNotBuyable.jsp. 6. Insert the following code: <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <?xml version="1.0" encoding="UTF-8"?> <object objectType="Product"> <catentryId>${param.catentryId}</catentryId> <object objectType="CatalogEntryExtraProperties"> <extraPropertiesId>${param.extraPropertiesId} </extraPropertiesId> <xprop_buyable>0</xprop_buyable> </object> </object>
115
A nice surprise here is that the new action is automatically enabled for multiple objects. The framework does all of the hard work that is related to that; therefore, we did not have to write any special code for dealing with multiple input objects. We also enabled the Manufacturer column for filtering, which is easy to turn on and off. The feature is quite useful, for instance, when the business user needs to update products from certain manufacturer(s) only. As a quick summary, this particular scenario was really quick to implement, but we also encountered some limitations in the FEP 3.0.1 version in this area. If we want to ask the user for additional input, for example, provide a new name for the
116
Manufacturer, there is no simple way of doing that. The custom action is mapped to the BOD service directly, and at the time of writing this book, the action could not be intercepted by additional client side dialogs. However, this is likely to change in future Management Center releases, allowing more flexible customized services to be built, while still maintaining the capability to target multiple objects as easily as in this example.
117
118
Chapter 8.
119
The advanced search facility that is supported out-of-the-box is for searching Catalog Entries and Categories.The advanced search is available as one of the options from the simple search pull-down menu. Figure 8-2 on page 121 is an example of an advanced search panel.
120
The requirement that we describe in this chapter is to include a new advanced search functionality for searching catalog entries on merchandising associations. The search parameters are the Code or the part number of the source catalog entry and the merchandising association type. It returns the list of catalog entries that have the specific code and at least one merchandising association to another catalog entry of the association type that is specified. The Code of the source catalog entry supports wild card search. In this chapter, we also include building a simple search facility for the same. The input for the simple search is the merchandising association type, which returns the list of catalog entries that have at least one merchandising association of the specified type. Simple search is included only for the completeness of the scenario and does not support wild card search.
121
The advanced panel for merchandising association looks like Figure 8-3.
122
123
4. Type in the File Name as FindMerchandisingAssociationsSearchDefinition.lzx. 5. In FindMerchandisingAssociationsSearchDefinitions.lzx, define the class as in Example 8-1.
Example 8-1 Merchandising associations search class definition
<class name="catFindMerchandisingAssociationsSearchDefinition" extends="wcfSearchDefinition" searchType="FindMerchandisingAssociations" displayName="${extCatalogResources.merchandisingAssociationsSearchDisplayName.string}" listClass="catCatalogEntrySearchGrid" listTitle="${extCatalogResources.merchandisingAssociationsSearchListTitle.string}" advancedSearchContentClass="catMerchandisingAssociationsAdvancedSearchContent"> <wcfSearchService name="findAllMerchandisingAssociations" url="/cmc/FindMerchandisingAssociations"> <wcfServiceParam name="storeId"/> <wcfServiceParam name="masterCatalogId"/> </wcfSearchService> </class> Here we extend the wcfSearchDefinition to define a new search type for Catalog Entries: searchType displayName listClass listTitle Defines the name of the business object search type. The display name for this search type in the simple and advanced search options. The grid class that displays the result of business objects that are returned from the service. The title for the search results that are displayed in the grid.
advancedSearchContentClassA free form view class that captures all of the advanced search user inputs. We specify a wcfSearchService to define our new search service call. The struts layer of the Management Center Web application maps the URL to a
124
jsp. wcfServiceParams are used to specify the parameters that will be passed as part of the service call, which are resolved at the level of the business object or at the context level. 6. Save and close the file. 7. Select File New Other Simple File. 8. Enter the parent folder as LOBTools/WebContent/WEB-INF/src/lzx/redbooks/catalog/searchDefinitio ns 9. Type the File Name as MerchandisingAssociationsAdvancedSearchContent.lzx 10.Define the advanced search content class, as shown in Example 8-2.
Example 8-2 Merchandising associations advanced search content class
<class name="catMerchandisingAssociationsAdvancedSearchContent" extends="wcfAdvancedSearchContent"> <simplelayout axis="y"/> <view name="section1" width="100%" height="15"> <simplelayout axis="y"/> <view name="filler1" height="15"/> <!-- Instructional text to the user --> <text text="${catalogResources.searchInstructionalText.string}" width="100%" fontstyle="italic"/> <view name="filler2" height="15"/> </view> <view name="section2" width="410" height="52"> <simplelayout axis="x"/> <view name="section2_1" width="56%" height="100%"> <simplelayout axis="y"/> <view name="filler1" height="30"/> <view name="section2_1_1" width="100%" height="30%"> <text text="${extCatalogResources.catalogEntrySourceCodeSearchPrompt.strin g}" width="177"/> </view> <view name="section2_1_2" width="97%" height="70%"> <!-- Text field for the catalogEntryCode search parameter --> <wcfInputText name="sourceCatalogEntryCode" width="177"/>
125
</view> </view> <view name="section2_2" width="56%" height="100%"> <simplelayout axis="y"/> <view name="filler2" height="30"/> <view name="section2_2_1" width="100%" height="30%"> <text text="${extCatalogResources.associationNameSearchPrompt.string}" width="180"/> </view> <wcfBaseComboBox name="associationName" width="180"> <handler name="oninit"> this.addItem(catalogResources.merchandisingAssociationName_ACCESSORY .string,catalogResources.merchandisingAssociationName_ACCESSORY.stri ng); this.addItem(catalogResources.merchandisingAssociationName_XSELL.str ing,catalogResources.merchandisingAssociationName_XSELL.string); this.addItem(catalogResources.merchandisingAssociationName_UPSELL.st ring,catalogResources.merchandisingAssociationName_UPSELL.string); this.addItem(catalogResources.merchandisingAssociationName_REPLACEME NT.string,catalogResources.merchandisingAssociationName_REPLACEMENT. string); this.selectItem(catalogResources.merchandisingAssociationName_ACCESS ORY.string); </handler> </wcfBaseComboBox> </view> </view> <method name="setSearchOptions"> <![CDATA[ super.setSearchOptions(); var newSearchOptions = {}; newSearchOptions.sourceCatalogEntryCode = this.section2.section2_1.section2_1_2.sourceCatalogEntryCode.getText (); newSearchOptions.associationName = this.section2.section2_2.associationName.getValue();
126
this.searchOptions = newSearchOptions; ]]> </method> </class> The catMerchandisingAssociationsAdvancedSearchContent is a free form view class that uses different widgets to elicit user input; however, it needs to extend from wcfAdvancedSearchContent. In this example, we have wcfInputText to provide a text box for the user to specify the catalog entry code, and we have wcfBaseComboBox for the user to choose from the merchandising association types. We must override the setSearchOptions method to specify the search parameters and their values that will be passed as part of the service call, for example, we specify that the search parameter sourceCatalogEntryCode takes the text from the sourceCatalogEntryCode widget. 11.Save and close the file. 12.In the Project Explorer, navigate to Dynamic Web Projects LOBTools WebContent WEB-INF src lzx commerce catalog. 13.Open the CatalogManagementToolDefinition.lzx for editing. 14.For the Management Center to recognize our new search definition, add the class definition to the CatalogManagementToolDefinition.lzx. Include the line in Example 8-3 in the lzx file.
Example 8-3 Merchandising associations class definition declaration
<catFindMerchandisingAssociationsSearchDefinition/> 15.Save and close the file. 16.In the Project Explorer, navigate to Dynamic Web Projects LOBTools WebContent WEB-INF src lzx commerce catalog. 17.Update the catalog extensions library file, as shown in Example 8-4 to point to the new search files.
Example 8-4 Including merchandising associations search files to the library
127
In this section, we create a new resource bundle and a corresponding properties file to hold translatable text. We advise you to create your own resource bundles and not modify the ones that come out-of-the box. 1. Create a properties file: a. In the Project Explorer, navigate to Dynamic Web Projects LOBTools Java Resources src. b. Right-click src, and select New Package. c. In the Name field, enter com.redbooks.commerce.client.lobtools.properties as the package name, and click Finish. The package is created. Note: The package might already exist if you went through any of the other examples that we gave in this book. If that is the case, ignore the steps a to c and proceed to the step d. d. Right-click the com.redbooks.commerce.client.lobtools.properties package, select New Other Simple File, and click Next. e. In the File Name field, enter CatalogLOB.properties, and click Finish. The CatalogLOB.properties file opens. f. Define the properties, as shown in Example 8-5.
Example 8-5 Merchandising associations search properties
merchandisingAssociationsSearchListTitle=Source Catalog Entries merchandisingAssociationsSearchDisplayName=Merchanding Associations catalogEntrySourceCodeSearchPrompt=Catalog Entry Source Code associationNameSearchPrompt=Association Name g. Save and close the file. 2. Register the new properties file in the resource bundle: a. In the Project Explorer, navigate to Dynamic Web Projects LOBTools WebContent WEB-INF src lzx redbooks catalog. b. Right-click catalog, select New Other Simple File, and click Next. c. In the File name field, enter extCatalogManagementResourceBundle.lzx. d. Click Finish. The extCatalogManagementResourceBundle.lzx file opens.
128
<class name="extCatalogResourceBundle" extends="wcfResourceBundle" baseName="com.redbooks.commerce.client.lobtools.properties.Catalo gLOB"> <wcfResourceBundleKey name="merchandisingAssociationsSearchDisplayName"/> <wcfResourceBundleKey name="merchandisingAssociationsSearchListTitle"/> <wcfResourceBundleKey name="catalogEntrySourceCodeSearchPrompt"/> <wcfResourceBundleKey name="associationNameSearchPrompt"/> </class> <extCatalogResourceBundle id="extCatalogResources"/> f. Save and close the file. 3. Include extCatalogManagementResourceBundle.lzx in the extension library so that the Management Center recognizes that it is available for use: a. In the Project Explorer, navigate to Dynamic Web Projects LOBTools WebContent WEB-INF src lzx commerce catalog. b. Open the CatalogExtensionsLibrary.lzx file. c. Update the file to point to the new resource bundle file, as shown in Example 8-7.
Example 8-7 Including the resource bundle in the library
129
To create a new controller jsp for the new search service: 1. Navigate to LOBTools WebContent jsp. 2. To create the JSP extension folder, right-click jsp, and select New Other Simple Folder. 3. In the Folder name field, type redbooks, click Next, and then click Finish. 4. Repeat steps 2 and 3 to create a catalog folder in the redbooks directory. Note: If you went through any of the other examples that we gave in this book, the package might already exist; therefore, ignore steps 2 to 4, and proceed to step 5. 5. To create the extension controller JSP file, right-click the catalog folder, and select New Other Simple File. In the File name field, type FindMerchandisingAssociations.jsp, and then click Finish. 6. Define the JSP, as shown in Example 8-8.
Example 8-8 Merchandising associations search jsp
<c:choose> <c:when test="${! (empty param.searchText )}"> <c:set var="expressionBuilderName" value="findSourceMerchandisingAssocationsAdvancedSearch"/> <c:set var="sourceCatentryCode" value="*"/> <c:set var="associationName" value="${param.searchText}"/> </c:when> <c:when test="${empty param.searchText}"> <c:set var="expressionBuilderName" value="findSourceMerchandisingAssocationsAdvancedSearch"/> <c:set var="sourceCatentryCode" value="${param.sourceCatalogEntryCode}"/> <c:set var="associationName" value="${param.associationName}"/> </c:when> </c:choose>
130
varShowVerb="showVerb" recordSetStartNumber="${param.recordSetStartNumber}" recordSetReferenceId="${param.recordSetReferenceId}" maxItems="${param.maxItems}"> <wcf:contextData name="storeId" data="${param.storeId}"/> <wcf:contextData name="catalogId" data="${catalog}"/> <wcf:param name="sourceCatentryCode" value="${sourceCatentryCode}"/> <wcf:param name="associationName" value="${associationName}"/> </wcf:getData>
The getData tag is part of the WebSphere Commerce foundation tag library. It allows us to retrieve service data objects from any WebSphere Commerce service and associate them with scripting variables: type Specifies the type of business object that the service returns. Here the returning BusinessObjectDocument contains an array of com.ibm.commerce.catalog.facade.datatypes.Catalog EntryType in its data area. Specifies the name of the exported scripting variable to which the array of CatalogEntryType returned is bound. This variable is then used elsewhere in the jsp to access the returned result. An expression builder constructs an XPath expression to retrieve data. Specifies the name of the exported scripting variable. This variable holds the Show verb object that is retrieved from the services.
var
expressionBuilder varShowVerb
recordSetStartNumber, recordSetReferenceId, maxItems These parameters are used for pagination. The SerializeCatalogEntries.jspf is then used to build the XML response that is sent back to the OpenLaszlo application in the Web browser.
131
7. Save and close the file. To extend the struts file to include an action element for the jsp that we defined earlier: 1. Navigate to LOBTools WebContent WEB-INF, and double-click the struts-extension.xml file to open it in the default editor. 2. Include the element in Example 8-9 under the <action-mapping> tag.
Example 8-9 Merchandising associations search struts action
<action path="/FindMerchandisingAssociations" forward="/jsp/redbooks/catalog/FindMerchandisingAssociations.jsp" /> This forwards the control to the JSP when the URL is as specified in the path element. 3. Save and close the file. To build an expression builder that makes the new search request to the server: 1. Right-click the LOBTools\WebContent\WEB-INF\config folder. 2. Click New Package. In the Name field, enter com.redbooks.commerce.catalog as the package name, and click Finish. The package is created. Note: If you went through any of the other examples that we gave in this book, the package might already exist; therefore, ignore steps 2, and proceed to step 5. 3. Right-click the com.redbooks.commerce.catalog folder. 4. Click New Other Simple File. Enter the file name as get-data-config.xml. 5. Define the XML file, as shown in Example 8-10.
Example 8-10 Merchandising associations search expression builder
132
<client-facade> <data-type-name>CatalogEntry</data-type-name> <class>com.ibm.commerce.catalog.facade.client.CatalogFacadeClient</c lass> <method>getCatalogEntry</method> </client-facade> <expression-builder> <name>findSourceMerchandisingAssocationsAdvancedSearch</name> <data-type-name>CatalogEntry</data-type-name> <class>com.ibm.commerce.catalog.internal.client.taglib.util.CatalogS earchExpressionBuilder</class> <method>formatExpression</method> <param> <name>template</name> <value>/CatalogEntry[Association[(Name='$associationName$')] and search(CatalogEntryIdentifier/ExternalIdentifier/PartNumber='$source CatentryCode$')]</value> </param> <param> <name>accessProfile</name> <value>Redbooks_CatalogEntry_MerchandisingAssociationsSearch</value> </param> </expression-builder>
All expression builders are defined in files that are named as get-data-config.xml (WebSphere Commerce getData action searches for configuration files that are named get-data-config.xml in the WEB-INF/config directory): The data-type element declares a supported data type for the getData action. The client-facade element declares the component client facade that invokes the service to fetch the data. The expression builder handles building the XPath expression with the published input parameters. 6. Save and close the file.
133
BEGIN_SYMBOL_DEFINITIONS <!-- CATENTRY table --> COLS:CATENTRY=CATENTRY:* COLS:CATENTRY_ID=CATENTRY:CATENTRY_ID COLS:CATENTRY:MARKFORDELETE=CATENTRY:MARKFORDELETE <!-- CATALOG table --> COLS:CATALOG=CATALOG:* COLS:CATALOG_ID=CATALOG:CATALOG_ID
134
<!-- CATGROUP table --> COLS:CATGROUP=CATGROUP:* COLS:CATGROUP_ID=CATGROUP:CATGROUP_ID <!-- CATENTDESC table --> COLS:CATENTDESC=CATENTDESC:* <!-- Other tables --> COLS:CATENTREL=CATENTREL:* COLS:MASSOCCECE=MASSOCCECE:* COLS:CATENTRY_ID_TO=MASSOCCECE:CATENTRY_ID_TO COLS:CATENTRY_ID_FROM=MASSOCCECE:CATENTRY_ID_FROM END_SYMBOL_DEFINITIONS This section defines the tables our query template to use. The column symbol definitions are used and referenced in the SELECT list of our SQL template statements. Physical schema changes require changes only to this section of the tpl file. 6. Copy and paste the SQL definitions in Example 8-12 after the symbol definitions into the same file.
Example 8-12 Merchandising associations search SQL definitions
BEGIN_XPATH_TO_SQL_STATEMENT name=/CatalogEntry[Association[(Name=)] and search()] base_table=CATENTRY sql= SELECT MASSOCCECE.$COLS:CATENTRY_ID_FROM$ FROM CATENTRY JOIN MASSOCCECE ON (CATENTRY.CATENTRY_ID = MASSOCCECE.CATENTRY_ID_FROM AND MASSOCCECE.STORE_ID IN ($STOREPATH:catalog$)), $ATTR_TBLS$ WHERE MASSOCCECE.MASSOCTYPE_ID IN (?Name?) AND ( $ATTR_CNDS$ ) ORDER BY
135
This section directly maps the logical and physical layers, that is, the XPath key directly to a SQL statement. Here for our XPath, we do a join on CATENTRY and MASSOCCECE in the SQL statement. 7. Copy and paste the associated SQLs in Example 8-13 to the same file after the SQL definitions.
Example 8-13 Merchandising associations search associated SQLs
BEGIN_ASSOCIATION_SQL_STATEMENT name=IBM_CatalogEntryDescriptionWithPriceAssocSQL base_table=CATENTRY additional_entity_objects=true sql= SELECT CATENTRY.$COLS:CATENTRY$, CATENTDESC.$COLS:CATENTDESC$, LISTPRICE.$COLS:LISTPRICE$ FROM CATENTRY LEFT OUTER JOIN CATENTDESC ON (CATENTDESC.CATENTRY_ID = CATENTRY.CATENTRY_ID AND CATENTDESC.LANGUAGE_ID IN ($CONTROL:LANGUAGES$)) LEFT OUTER JOIN LISTPRICE ON (CATENTRY.CATENTRY_ID = LISTPRICE.CATENTRY_ID) WHERE CATENTRY.CATENTRY_ID IN ($ENTITY_PKS$) END_ASSOCIATION_SQL_STATEMENT BEGIN_ASSOCIATION_SQL_STATEMENT name=IBM_CatalogEntryToCatalogGroupRelationship base_table=CATENTRY additional_entity_objects=true sql= SELECT CATENTRY.$COLS:CATENTRY$, CATGPENREL.$COLS:CATGPENREL$, CATGROUP.$COLS:CATGROUP$
136
FROM CATENTRY, CATGPENREL JOIN CATGROUP ON (CATGROUP.CATGROUP_ID = CATGPENREL.CATGROUP_ID AND CATGPENREL.CATALOG_ID = $CTX:CATALOG_ID$) WHERE CATENTRY.CATENTRY_ID IN ($ENTITY_PKS$) AND CATGPENREL.CATENTRY_ID =CATENTRY.CATENTRY_ID AND CATGROUP.MARKFORDELETE = 0 END_ASSOCIATION_SQL_STATEMENT BEGIN_ASSOCIATION_SQL_STATEMENT name=IBM_ParentCatalogEntryForRootRelationships base_table=CATENTRY additional_entity_objects=true sql= SELECT CATENTRY.$COLS:CATENTRY$, CATENTDESC.$COLS:CATENTDESC$, CATENTREL.$COLS:CATENTREL$, CATENTRY_PARENT.$COLS:CATENTRY_ID$, CATENTRY_PARENT.$COLS:CATENTRY:MEMBER_ID$, CATENTRY_PARENT.$COLS:CATENTRY:ITEMSPC_ID$, CATENTRY_PARENT.$COLS:CATENTRY:CATENTTYPE_ID$, CATENTRY_PARENT.$COLS:CATENTRY:PARTNUMBER$ FROM CATENTRY,CATENTREL JOIN CATENTRY CATENTRY_PARENT ON CATENTRY_PARENT.CATENTRY_ID = CATENTREL.CATENTRY_ID_PARENT LEFT OUTER JOIN CATENTDESC ON CATENTDESC.CATENTRY_ID = CATENTREL.CATENTRY_ID_PARENT AND CATENTDESC.LANGUAGE_ID IN ($CONTROL:LANGUAGES$) WHERE CATENTRY.CATENTRY_ID IN ($ENTITY_PKS$) AND CATENTREL.CATENTRY_ID_CHILD = CATENTRY.CATENTRY_ID AND CATENTRY.MARKFORDELETE = 0 AND CATENTRY_PARENT.MARKFORDELETE = 0 END_ASSOCIATION_SQL_STATEMENT The associated SQL statements define a specific SQL query. These queries are then reused to build our access profiles in the PROFILE section.
137
Here we have three associated SQL statements: IBM_CatalogEntryDescriptionWithPriceAssocSQL (joins on CATENTRY, CATENTDESC and LISTPRICE) IBM_CatalogEntryToCatalogGroupRelationship (joins on CATENTRY, CATGPENREL and CATGROUP) IBM_ParentCatalogEntryForRootRelationships (joins on CATENTRY, CATENTDESC and CATENTREL) 8. Copy and paste the profile definitions in Example 8-14, after the associated SQL definitions, in the same file.
Example 8-14 Merchandising associations search profile definitions
BEGIN_PROFILE name=Redbooks_CatalogEntry_MerchandisingAssociatonsSearch BEGIN_ENTITY base_table=CATENTRY className=com.ibm.commerce.foundation.internal.server.services.dataa ccess.graphbuilderservice.DefaultGraphComposer associated_sql_statement=IBM_CatalogEntryDescriptionWithPriceAssocSQ L associated_sql_statement=IBM_CatalogEntryToCatalogGroupRelationship associated_sql_statement=IBM_ParentCatalogEntryForRootRelationships END_ENTITY END_PROFILE Here we use the associated SQL statements to define our access profile. 9. Save and close the file.
138
b. Click the Merchandising Associations tab. c. Enter a value in the Catalog Entry Source Code field (use '*' to indicate wild card search), for example, type FUCO*. d. From the Association Name combo box, choose a value for the merchandising associations type value, for example, choose ACCESSORY from the combo box, as shown in Figure 8-4.
e. Click Search. A grid with all of the catalog entries whose part number starts with FUCO and with one or more merchandising associations with another catalog entry of type ACCESSORY are displayed. 3. Test the merchandising associations simple search function: a. From the drop-down menu on the simple search box, choose the Merchandising Associations option, as shown in Figure 8-5 on page 140.
139
b. Type the merchandising association type for which you want to search, for example, type in X-SELL. Wild cards are not accepted here. c. Click the Search button. A grid with all of the catalog entries that have one or more merchandising associations with another catalog entry of type 'X-SELL' are displayed, as shown in Figure 8-6.
140
141
142
Chapter 9.
143
144
<CNETResponse version="1.0"> <TechProducts start="0" numReturned="1" numFound="145"> <TechProduct id="32069546" xlink:href="http://api.cnet.com/restApi/v1.0/techProduct? productId=32069546&iod=hlPrice"> <Name>Lenovo ThinkPad T61</Name> <ImageURL width="60"></ImageURL> . . .
145
<LowPrice>$925.50</LowPrice> <HighPrice>$1,738.00</HighPrice> <PreferredNode id="6490"/> <Category id="6490" xlink:href="http://api.cnet.com/restApi/v1.0/category? categoryId=6490"/> <PreferredNode id="6490"/> <Offers start="0" numReturned="3" numFound="3"> <Offer> <Merchant id="6274490"> <Name>Lenovo</Name> <Rating outOf="5">5.0</Rating> <ImageURL> http://i.i.com.com/cnwk.1d/sc/6274490-4-3-9-0-0.gif </ImageURL> </Merchant> <MerchandiseType/> <OfferDate>2008-06-12 15:44:50.0</OfferDate> <Price lowest="false">$925.50</Price> <Availability>Yes</Availability> </Offer> <Offer> . . </Offer> </Offers> </TechProduct> </TechProducts> </CNETResponse>
Example 9-2 Sample error response
<CNETResponse version="1.0"> <Error code="401"> <ErrorMessage>A developer key is required to access the CNET API. To get your FREE developer key, go to this URL: http://membership.cnet.com/1383-4_1-172.html?path=https%3A%2F%2Ffanyv88.com%3A443%2Fhttp%2Fapi.cnet .com%2Fdashboard.html </ErrorMessage> <Error> </CNETResponse>
146
In the following sections, we step through the customizations that you must perform to implement the price comparison widget.
147
Note: The following sections include code snippets along with explanations for the main parts of this customization. For complete source code, refer to the zip archive that we provided for this example. The contents of the zip archive might be imported into your workspace under the LOBTools project. Some of the files in this archive might already exist in your workspace if you did other examples in this Redbooks publication or if we are making changes to files that are part of the WebSphere Commerce product. In such cases, we recommend that you merge the changes manually instead of overwriting files in your workspace with the files that we provided for this example.
<library> <class name="extCatalogResourceBundle" extends="wcfResourceBundle" baseName="com.redbooks.commerce.client.lobtools.properties.CatalogLOB"> <!-- Price Comparison component resource bundle keys --> <wcfResourceBundleKey name="priceTab"/> <wcfResourceBundleKey name="priceListTitle"/> <wcfResourceBundleKey name="merchantColumnTitle"/> <wcfResourceBundleKey name="priceColumnTitle"/> <wcfResourceBundleKey name="noPriceFound"/> <wcfResourceBundleKey name="numberOfPricesFound"/> <wcfResourceBundleKey name="serviceError"/> <wcfResourceBundleKey name="priceRange"/> <wcfResourceBundleKey name="serviceKey"/> <wcfResourceBundleKey name="serviceURL"/>
148
</class> <extCatalogResourceBundle id="extCatalogResources"/> </library> The resource bundle file CatalogLOB.properties is created in the package com.redbooks.commerce.client.lobtools.properties, which is located under the LOBTools/src folder, as shown in Example 9-4.
Example 9-4 Resource bundle content
#--Price Comparison component labels priceTab=Price priceListTitle=Price List merchantColumnTitle=Merchant priceColumnTitle=Price #--Price Comparison component messages noPriceFound=No offers found. numberOfPricesFound={number} offers found. priceRange=Price range: {minimumPrice} to {maximumPrice}. serviceError=Unable to retrieve offers from service provider. #--Price Comparison component CNET Developer key. Register with CNET to get a developer key and enter the value here. serviceKey=<ENTER YOUR KEY HERE> #--Price Comparison component CNET Service URL. DO NOT CHANGE THIS. serviceURL=http://api.cnet.com/restApi/v1.0/techProductSearch?partKey={ serviceKey}&query={partnumber}&criteria=minOffers%3D1%7ChasGoodBad%3Dtr ue&iod=hlPrice%2Coffers%2CgoodBad&orderBy=price&sortDesc=true&start=0&r esults=10
149
150
<!-- HTTP data set to issue requests to CNET service --> <dataset name="extCatalogProductPriceGrabberData" type="http" request="false" querytype="get" > <handler name="onerror" args="a"> <![CDATA[ classroot.setAttribute("errorMessage", extCatalogResources.serviceError.string); ]]> </handler> <handler name="ontimeout" args="a"> <![CDATA[ classroot.setAttribute("errorMessage", extCatalogResources.serviceError.string); ]]> </handler> </dataset>
151
<extPriceGrabberGrid datapath="local:classroot.extCatalogProductPriceGrabberData:/CNETRespon se/" contentdatapath="TechProducts/TechProduct[1]/Offers[1]/Offer" rowheight="50" selectable="false" showhlines="true" showhscroll="false" width="${parent.width}" height="${parent.height-21}"> <!-- Merchant logo column --> <extPriceGrabberGridcolumn sortable="false" width="250"> <image resource="$path{ 'Merchant/ImageURL/text()' }" valign="middle" x="$once{parent.x+5}"></image> </extPriceGrabberGridcolumn> <!-- Merchant name column --> <extPriceGrabberGridcolumn width="250" text="${extCatalogResources.merchantColumnTitle.string}"> <text visible="true" width="${parent.width}" valign="middle"
152
resize="false" datapath="Merchant/Name/text()" /> </extPriceGrabberGridcolumn> <!-- Offer price column --> <extPriceGrabberGridcolumn width="200" text="${extCatalogResources.priceColumnTitle.string}"> <text visible="true" width="${parent.width}" valign="middle" resize="false" datapath="Price/text()" /> </extPriceGrabberGridcolumn> <extPriceGrabberGridcolumn width="900"> </extPriceGrabberGridcolumn> </extPriceGrabberGrid>
Status area
The status area is displayed right below the custom grid. It uses two text classes: one to display the price range message or error message and another to display the offer count message, as shown in Example 9-7 on page 154. The message text that is displayed comes from the following attributes of the price comparison class: successMessage: This attribute holds the price range message for a successful request. offerMessage: This attribute holds the offer count message for a successful request. errorMessage: This attribute holds error message to be displayed (if any). If the errorMessage attribute has a value, both successMessage and offerMessage values are empty. We use constraints to create a dependency between the text classes in the status area and the message attributes. The following constraints are defined on the text attribute of the status area text classes: The first text class has a constraint that checks the successMessage and errorMessage attribute lengths and displays the attribute that has a value. When either of these attributes is modified, this constraint is evaluated. The second text class has a constraint that displays the offerMessage attribute. This constraint is evaluated when the offerMessage attribute is modified. The success/error message text class also defines a handler method for its ontext event. When the text value that is displayed is modified, this handler
153
method changes the text foreground color to red if the content is an error message.
Example 9-7 Grid status area
<!-- Status area, displays success/error message and offer count --> <view width="${parent.width}"> <view y="-1" width="${parent.width}" resource="gridFooterResource" stretches="width"> </view> <text x="${parent.x+5}" selectable="true" y="0" text="$always{classroot.successMessage.length > 0 ? classroot.successMessage : classroot.errorMessage}"> <handler name="ontext"> if (classroot.successMessage.length > 0) { this.setColor(0x000000); } else { this.setColor(0xff0000); } </handler> </text> <text x="${parent.width-this.width-5}" y="0" text="$always{classroot.offerMessage}"> </text> </view>
Datapointer declaration
We create a data pointer to point to the root node of the XML response from the HTTP dataset, as shown in Example 9-8 on page 155. This datapointer inspects the response XML and sets the status message attributes successMessage, offerMessage and errorMessage. The datapointer registers the method setResultSize to be called when the ondata event is fired. The setResultSize method uses the following XPATH expressions to format status messages: Error/ErrorMessage: Service error message (if any) from the CNET API. If a value is returned by this XPATH expression, we set the errorMessage attribute. TechProducts/@numReturned: This XPATH expression returns the number of products in the response XML. If the value of this expression is zero, then
154
product data does not exist in CNET. In this case, we set the errorMessage attribute. TechProducts/TechProduct[1]/Offers[1]/@numReturned: This XPATH expression returns the number of prices that are available for the product. If the value of this expression is zero, we set the error message attribute; otherwise, we set the offerMessage attribute. TechProducts/TechProduct[1]/LowPrice: This XPATH expression retrieves the lowest price. TechProducts/TechProduct[1]/HighPrice: This XPATH expression retrieves the highest price. If both the lowest price and the highest price are available, we set the price range message in the successMessage attribute.
Example 9-8 Datapointer to set status message
<datapointer name="resultDataPointer" xpath="local:classroot.extCatalogProductPriceGrabberData:/CNETResponse" ondata="setResultSize()" > <method name="setResultSize"> <![CDATA[ var error = this.xpathQuery( 'Error/ErrorMessage/text()' ); if (error != null && error.length > 0) { if (error.indexOf('.') != -1) error = error.substring(0, error.indexOf('.')); classroot.setAttribute("errorMessage", error); return; } var productCount = this.xpathQuery( 'TechProducts/@numReturned' ); var offerCount = this.xpathQuery( 'TechProducts/TechProduct[1]/Offers[1]/@numReturned' ); if (productCount != null && offerCount != null && productCount > 0 && offerCount > 0) { classroot.setAttribute("offerMessage", classroot.replaceStringValue(extCatalogResources.numberOfPricesFound.st ring, "{number}", offerCount)); var lowPrice = this.xpathQuery( 'TechProducts/TechProduct[1]/LowPrice/text()' ); var highPrice = this.xpathQuery( 'TechProducts/TechProduct[1]/HighPrice/text()' ); if (lowPrice != null && lowPrice.length > 0 && highPrice != null && highPrice.length > 0) { classroot.setAttribute("successMessage",
155
classroot.replaceStringValue( classroot.replaceStringValue(extCatalogResources.priceRange.string, "{minimumPrice}", lowPrice), "{maximumPrice}", highPrice)); } } else { classroot.setAttribute("errorMessage", extCatalogResources.noPriceFound.string); } ]]> </method> </datapointer>
156
<!-- Return PriceGrabber component as editor --> <method name="createEditor" args="parentComponent"> <![CDATA[ var availableWidth = parentComponent.width parentComponent.promptWidth - parentComponent.xspacing; new extCatalogProductPriceGrabber(parentComponent, { name: "pricelist", x: parentComponent.promptWidth + parentComponent.xspacing, enabled: parentComponent.enabled, width: availableWidth, property: this.o.getPropertyValue("partnumber","") }); ]]> </method> setModelObject: Overrides setModelObject (Example 9-10) in the parent class to: Set the property attribute of the price comparison widget when the product object selection changes. Create and register a LzDelegate to listen to onvalue events for the partnumber property of the product object. When the partnumber value is changed, this delegate calls the updatePropertyObject method.
Example 9-10 setModelObject method
<method name="setModelObject" args="newObject"> <![CDATA[ super.setModelObject(newObject); var newProperty = null; if (typeof(this.o) != "undefined" && this.o != null) { newProperty = this.o.getPropertyValue("partnumber",""); } if (this["pricelist"]) { this.pricelist.setAttribute("property", newProperty); } var newPropertyObject = this.o.getProperty("partnumber","",true); if (typeof(this["propertyObject"]) == "undefined" || newPropertyObject != this["propertyObject"]) { if (typeof(this.updatePropertyObjectDel) == "undefined") { this.updatePropertyObjectDel = new LzDelegate(this, "updatePropertyObject"); }
157
this.updatePropertyObjectDel.unregisterAll(); this.propertyObject = newPropertyObject; if (this.propertyObject != null) { this.updatePropertyObjectDel.register(this.propertyObject, "onvalue"); if (this.isinited) { this.updatePropertyObject(); } } } ]]> </method> updatePropertyObject: Called when the partnumber property of a product is modified. This method, shown in Example 9-11, sets the property attribute of the price comparison widget with the new partnumber value.
Example 9-11 updatePropertyObject method
<method name="updatePropertyObject"> <![CDATA[ if (typeof(this.propertyObject) != "undefined" && this.propertyObject != null && typeof(this.pricelist) != "undefined") { this.pricelist.setAttribute("property", this.propertyObject.value); } ]]> </method> destroy: Deletes the LzDelegate that this class creates. See Example 9-12.
Example 9-12 destroy method
<method name="destroy"> <![CDATA[ if (this["updatePropertyObjectDel"]) { this.updatePropertyObjectDel.unregisterAll(); delete this.updatePropertyObjectDel; } super.destroy(); ]]> </method>
158
<class extends="wcfPropertyGroup" groupTitle="${catalogResources.pricingSection.string}" name="extCatalogProductPricePropertyGroup" open="true"> <!-- Child List Editor: A table to display list price for the catalog entry in different currencies. --> <wcfChildListViewer name="listPriceClv" listClass="catListPriceObjectGrid" objectTypes="CatalogEntryListPrice" promptText="${catalogResources.listPricePrompt.string}" extendedHelpText="${catalogResources.extendedHelpText_listprice.string} "/> <!-- Child List Editor: A table to display different offers for the catalog entry in different currencies. --> <wcfPropertyChildListEditor name="offersCle" listClass="catOfferObjectGrid" objectTypes="CatalogEntryOffer" promptText="${catalogResources.offerPricePrompt.string}" extendedHelpText="${catalogResources.extendedHelpText_offerprice.string }"/> <!-- Price Grab Component: Display competitor prices for product. --> <extCatalogProductPriceGrabberComponent
159
promptText="${extCatalogResources.priceListTitle.string}" /> </class> Create a property pane, as shown in Example 9-14, to use the new property group, extCatalogProductPricePropertyGroup.
Example 9-14 Price property pane definition
<class name="catManageProduct" extends="wcfPropertyPane"> <catManageGeneralProductInformation/> <catManagePublishingInformation/> <catManageDisplayInformation/> <!-- Redbooks customization: Disable Pricing section in Manage product tab, this is moved to the new Price tab --> <!-- <catManagePricingInformation/> --> <!-- This tag is disabled by default. To enable it, uncomment the tag below --> <!-- <catManageAdditionalInformation/> --> </class>
160
extCatalogProductPricePane (See section Creating a new property pane on page 159). Note: Example 9-16 does not include the complete source of the catProductProperties class. Refer to the ProductPropertiesView.lzx file for the complete source code.
Example 9-16 Price tab pane
<class extends="wcfObjectProperties" name="catProductProperties"> <!-- This is the set of tabs to display on the Product properties view --> <wcfPropertyTabs name="tabs"> <!-- Tab: Manage Product. This tab contains general information about the selected product such as name, and description. --> <wcfPropertyTabPane name="manageProductTab" text="${catalogResources.manageProductTab.string}"> <!-- Property Pane: Manage Product. This is an instantiation of the property pane class which contains general product details. --> <catManageProduct/> </wcfPropertyTabPane> <!-Redbooks customization: New price tab pane. --> <!-- Tab: Price. This tab contains list/offer prices and competitor pricing information (if available) --> <wcfPropertyTabPane name="extManagePriceTab" text="${extCatalogResources.priceTab.string}"> <!-- Property Pane: Price. This is an instantiation of the proprty pane class which includes Price Grabber component, and list/offer prices child list viewers previously in Manage product tab. --> <extCatalogProductPricePane /> </wcfPropertyTabPane> . . . </wcfPropertyTabs> </class>
161
<!-- Catalog resource bundle extension --> <include href="../../redbooks/catalog/extCatalogManagementResourceBundle.lzx" /> <!-- Price grabber component --> <include href="../../redbooks/components/extPriceGrabberGrid.lzx" /> <include href="../../redbooks/components/extPriceGrabberGridColumn.lzx" /> <include href="../../redbooks/components/extPriceGrabber.lzx" /> <include href="../../redbooks/components/extPriceGrabberPropertyComponent.lzx" />
#--Price Comparison component CNET Developer key. Register with CNET to get a developer key and enter the value here. serviceKey=<ENTER YOUR KEY HERE> 3. Build the LOBTools project, and start the WebSphere Commerce server.
162
4. Load the sample technology products to the ConsumerDirect catalog. A catalog import CSV file is provided in the zip archive file for this example. Use the Catalog Import tool in WebSphere Commerce Accelerator to load and publish this data. This creates a category that is named LCD Television and also creates products under this category. Note: For detailed information about loading the catalog data using WebSphere Commerce Accelerator, refer to the following Web site: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com. ibm.commerce.user.doc/tasks/tcacsvld.htm 5. Launch the Management Center. 6. Open the Catalog tool, select the ConsumerDirect store, and navigate to the LCD Television category, as shown in Figure 9-3.
7. From the product list, open a product, and in the properties view, select the Price tab. This tab, shown in Figure 9-4 on page 164 should include the list price grid, offer price grid, and the new price comparison grid.
163
164
10
Chapter 10.
165
You can set the following global preferences using the Preferences dialog: Default store to be used when a tool is opened Default language to be used when a tool is opened Default cultural locale that determines the number format in the tool Whether to enable the Extended Help feature or not
166
The default language is persisted as the preferred language of the user in the USERS table (USERS.LANGUAGE_ID), which is located in the database. Other preferences are persisted in the database as custom member attributes on a per user basis. In Chapter 9, Price comparison mashup on page 143, we built a price comparison feature to compare product prices from different service providers. The requirement is to allow the business user to enable or disable the feature and, when required, without any IT intervention. To satisfy this requirement, we must customize the Preferences dialog and add a new user preference. Figure 10-3 shows how the customized Preferences dialog should appear.
Note: To add a new user preference, you should have at least Feature Pack 4 installed because the wcfUserPreferenceObject class is declared private in previous versions.
167
The following list provides information about the matching resources files from Figure 10-4: PreferenceManager.lzx: This file contains the wcfPreferenceManager class, which is the base class for storing and managing preferences. This class
168
handles all preferences that are captured in the Management Center, stores them as attributes in the class, and persists them as custom member attributes to the database. wcfPreferenceManager is a private class that we cannot modify. UserPreferenceDialog.lzx: This file contains the user preference dialog, which is opened when we select Preferences from the Management Center menu. The private class, wcfUserPreferenceDialog, contains the user preference panel and the OK and Cancel buttons. UserPreferenceObjects.lzx: This file contains the wcfUserPreferenceObject class, which is the base class that represents a user preference object. All preferences that are in the Management Center extend from this class, for example, wcfStorePreference defines the default store preference. For the new user preference, we create a new class that extends from wcfUserPreferenceObject and implement the methods that belong to this class. UserPreferencePanel.lzx: This file defines all of the widgets that are in the user preference dialog box. We include a widget to represent the option to enable or disable the price comparison feature.
169
e. In the File name field, enter extShellLOB.properties, and click Finish. The extShellLOB.properties file opens. f. Define the properties, as given in Example 10-2.
Example 10-1 Defining the properties file
userPreferencePriceComparisonPrompt=Enable price comparison feature 2. Register the new properties file in the resource bundle: a. In the Project Explorer, navigate to Dynamic Web Projects LOBTools WebContent WEB-INF src lzx. b. Right-click lzx, and select New Folder. c. In the File name field, enter redbooks, and click Finish. d. Right-click redbooks, and select New Folder. e. In the File name field, enter shell, and click Finish. f. Right-click shell, select New Other Simple File, and click Next. g. In the File name field, enter extShellResourceBundle.lzx. h. Click Finish. The extShellResourceBundle.lzx file opens. i. Add the code in Example 10-3 on page 171 to the file.
Example 10-2 Defining the resource bundle
<library> <class name="extShellResourceBundle" extends="wcfResourceBundle" baseName="com.redbooks.commerce.client.lobtools.properties.extShe llLOB"> <wcfResourceBundleKey name="userPreferencePriceComparisonPrompt" /> </class> <extShellResourceBundle id="extShellResources"/> </library> j. Save and close the file.
170
3. Include extShellResourceBundle.lzx in the extension library so that it is recognized by Management Center and available for use: Note: If you are using Feature Pack 3.0.1, edit ShellLibrary.lzx to fix a minor issue: 1. In the Project Explorer, navigate to Dynamic Web Projects LOBTools WebContent WEB-INF src lzx commerce shell restricted. 2. Open the ShellLibrary.lzx file. 3. Move the following entry to the end of the file just before the closing </library> tag: <include href=../ShellExtensionsLibrary.lzx /> This issue is fixed in Feature Pack 4. a. In the Project Explorer, navigate to Dynamic Web Projects LOBTools WebContent WEB-INF src lzx commerce shell. b. Open the ShellExtensionsLibrary.lzx file. c. Update the file, as shown in Example 10-4 on page 172 to point to the new resource bundle file.
Example 10-3 Including the resource bundle in the library
171
configurable properties that are used in Management Center, which includes the user preferences as well. c. Include the following attribute in Example 10-5 just before the closing </class> tag.
Example 10-4 Defining a default value for the preference
<attribute name=defaultPriceComparisonEnabled type=string value=true /> This value is the default for a user who does not have the user preferences set. d. Save the file. 2. Create the new class extPriceComparisonPreference: a. In the Project Explorer, navigate to Dynamic Web Projects LOBTools WebContent WEB-INF src lzx redbooks shell. b. Right-click shell, select New Other Simple File, and click Next. c. In the File name field, enter extUserPreferenceObjects.lzx. d. Click Finish. The extUserPreferenceObjects.lzx file opens. e. Paste the code in Example 10-5 into the file.
Example 10-5 Defining the new user preference object
<library> <class name="extPriceComparisonPreference" extends="wcfUserPreferenceObject"> <!--- Name of the preference. --> <attribute name="preferenceName" type="string" value="CMCPriceComparisonEnabled" /> <!--This method returns the value of this preference. @returns string: value of this preference --> <method name="getValue"> <![CDATA[ return this.priceComparisonEnabled.getValue().toString(); ]]> </method> <!---
172
This method sets the value of this preference from the map object in preference manager to the object in the user interface. --> <method name="loadValue"> <![CDATA[ if (preferenceManager.getPreference(this.preferenceName) == undefined) { preferenceManager.setPreference(this.preferenceName, configProperties.defaultPriceComparisonEnabled); } this.priceComparisonEnabled.setValue(preferenceManager.getPrefere nce(this.preferenceName)); ]]> </method> <wcfBaseCheckbox name=priceComparisonEnabled text="${extShellResources.userPreferencePriceComparisonPrompt.str ing}" /> </class> </library> f. Save the file. wcfUserPreferenceObject defines the following methods, which you can override to provide your own implementation: oninit applyValue getValue This handler is called an initialization of the user preference. This method applies the preference value to the global preference setting. This method returns the value of the preference. By default it returns null. We implement this method to return the value of the check box. This method loads the preference value from the preference manager to the object in the user interface. By default this method does nothing. We implement this method to load the saved value of the preference. If the logged-in user does not have this preference set, we load the default value from wcfConfigProperties.
loadValue
173
validateValue
This method validates the value of the preference that the user enters. By default this method returns true. We do not implement this method in our example because the user input is just a check box; however, it can be implemented if the users input is some text or a numerical value.
3. Include extUserPreferenceObjects.lzx in the extension library so that it is recognized by Management Center and available for use: a. In the Project Explorer, navigate to Dynamic Web Projects LOBTools WebContent WEB-INF src lzx commerce shell. b. Open the ShellExtensionsLibrary.lzx file. c. Include extUserPreferenceObjects.lzx, as shown in Example 10-6.
Example 10-6 Including the resource bundle in the library
<library> <include href=../../redbooks/shell/extUserPreferenceObjects.lzx /> </library> d. Save the file. 4. Include the new preference widget in the Preferences dialog. The file UserPreferencesPanel.lzx defines the Preferences dialog. We include the newly created preference object (created in Step 2) here so that it shows up on the Preferences dialog: a. In the Project Explorer, navigate to Dynamic Web Projects LOBTools WebContent WEB-INF src lzx commerce shell. b. Open the UserPreferencePanel.lzx file. c. At the end of the file, the class wcfUserPreferencePanel is defined, as shown in Example 10-8.
Example 10-7 Original wcfUserPreferencePanel
<class name="wcfUserPreferencePanel"> <wcfStorePreference /> <wcfLanguagePreference listClass="wcfLanguageList" enabled="false" /> <wcfLocalePreference listClass="wcfLocaleList" /> <wcfStylePreference listClass="wcfStyleList" visible="false" /> <wcfExtendedHelpPreference />
174
<simplelayout axis="y" spacing="15" /> </class> Include the extPriceComparisonPreference class, as shown in bold in Example 10-9.
Example 10-8 Customized wcfUserPreferencePanel
<class name="wcfUserPreferencePanel"> <wcfStorePreference /> <wcfLanguagePreference listClass="wcfLanguageList" enabled="false" /> <wcfLocalePreference listClass="wcfLocaleList" /> <wcfStylePreference listClass="wcfStyleList" visible="false" /> <wcfExtendedHelpPreference /> <extPriceComparisonPreference /> <simplelayout axis="y" spacing="15" /> </class>
<method name="init"> <![CDATA[ super.init(); var pref = preferenceManager.getPreference("CMCPriceComparisonEnabled"); if (pref == "true") { this.setAttribute("visible", true); } else { this.setAttribute("visible", false);
175
} ]]> </method> In the init method, we use the Preference Manager to retrieve the value of the user preference. If the value is true we set the visible attribute of extCatalogProductPriceGrabberComponent to true; otherwise, we set it to false. So if the user selected the check box against Enable price comparison feature in the Preferences dialog, the price comparison feature is enabled; otherwise, it is disabled. 4. When a user changes the preference, the Management Center should automatically update the price comparison widget to reflect the change. To achieve this we use an event handler. When a user preference changes, the Preference Manager sends an event called preferenceChanged. Any object that wants to be notified of a change of preferences can handle this event. To handle the event, add the code in Example 10-11 on page 177 to extCatalogProductPriceGrabberComponent just after the init method that we added in step 3.
Example 10-10 The event handler
<handler name="preferenceChanged" reference="preferenceManager" args="preference"> <![CDATA[ //if this event is for the CMCPriceComparisonEnabled preference if (preference == "CMCPriceComparisonEnabled") { this.init(); } ]]> </handler> In the event handler, we check if CMCPriceComparisonEnabled is the preference for which the current event was generated and call the init method in case it is. The init method will again check the preference and enable or disable the price grabber accordingly.
176
Preference Manager calls the service /cmc/SavePreferences to save the user preferences in the submitSavePreferences method. The file struts-ibm-foundation.xml contains all of the predefined URLs that the shell requires. The SavePreferences URL is mapped to the updatePerson method of MemberFacadeClient, as shown in Example 10-11.
Example 10-11 Service mapping in struts-ibm-foundation.xml
<action path="/SavePreferences" parameter="Person" type="com.ibm.commerce.foundation.client.facade.bod.servlet.struts.Busi nessObjectDocumentClientLibraryAction" className="com.ibm.commerce.foundation.client.facade.bod.servlet.struts .BusinessObjectDocumentActionMapping"> <set-property property="clientLibrary" value="com.ibm.commerce.member.facade.client.MemberFacadeClient" /> <set-property property="clientLibraryMethod" value="updatePerson" /> </action> The updatePerson method is mapped to the UserRegistrationUpdateCmd. Note: For more information about the Member services, refer to the WebSphere Commerce V6 Information Center: ChangePerson http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/index.jsp?top ic=/com.ibm.commerce.component-services.doc/refs/rmschangeperson.htm The UserRegistrationUpdateCmd stores the user preferences as custom member attributes in the MBRATTR and MBRATTRVAL tables. We need to define a custom attribute for the price grabber option. Execute the SQL statement in Example 10-12 to define the custom attribute.
Example 10-12 Defining the custom member attribute
insert into MBRATTR (MBRATTR_ID, ATTRTYPE_ID, NAME, DESCRIPTION) values (1,'STRING','CMCPriceComparisonEnabled','Default flag for Price Comparison feature in CMC'); CMCPriceComparisonEnabled is the name of the attribute that we defined in ConfigProperties.lzx in step 1 on page 171.
177
Note: For more information about defining custom member attributes, see Defining custom attributes for member URLs in the WebSphere Commerce Information Center. http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/index.jsp?top ic=/com.ibm.commerce.developer.doc/tasks/tmsdfatt.htm
178
11
Chapter 11.
Spell Checker
In this chapter, we build a spell checking feature that you can use to spell check and correct the text fields of the properties panels of the Management Center. The customizations for this feature are almost entirely within the OpenLaszlo code of the Presentation layer. We explore the capabilities of OpenLaszlo as a prototyping environment, followed by a progression, in example stages, to a final solution. We describe those stages in this section. This example, and the example in Chapter 9, Price comparison mashup on page 143, are both examples of a client-side mashup, which is where an external service is used to augment the functionality that is available within the client application. OpenLaszlo has some powerful features for handling the XML is returned from the type of external service that is used in a Mashup. You can easily filter the XML that is returned and bound to display widgets, and the example in this chapter demonstrates the use of these features. The dynamic nature of the spell checker scenario provides examples of how to use JavaScript code within OpenLaszlo to handle events, which is one of the more complex topics of OpenLaszlo programming.
179
The customer can edit the long description field using the rich text property editor, but the short description uses a plain text editor.
180
In a typical fit-gap analysis phase of a project, it is common to show the customer what facilities WebSphere Commerce has by default. For this example, we assume that, during this phase, the customer saw the window (Figure 11-1 on page 180), and said that they want to have: A spell check and correction feature. To check and correct short and long description fields. Capabilities that are similar to a word processing spell checker. Normally, there would be an analysis and cos of what this feature would take to implement. In this case, as it often is, there are considerations to be made on cost, time scale, and features, which we discuss in 11.2.1, Meeting requirements on page 183. Solution architects add detail to the requirements, discuss them with the customer, and get approval that the requirements include what they want. At this stage, it is important to point out to the customer that analysis is needed to determine what the cost, time scale, and feasibility implications are. Without investigation, we are unlikely to know this. A more detailed list of requirements would be something like this: Spell check and correction for any of the text fields of the property editor page, for all catalog entry types The ability to view and correct both plain and rich text Similar to Lotus Notes, in terms of capabilities, for a screen shot, as seen in Figure 11-2 on page 182. We want the ability to: Highlight unknown words Suggest corrections that we can select and use for replace Edit and replace the word Replace one occurrence of the misspelled word Replace all occurrences of the misspelled word within the text field Skip one occurrence Skip all occurrences View unknown words that are highlighted in the context of the original text Process the text field, one unknown word at a time, and the ability to click accept or cancel the changes that were made Add the unknown word to a dictionary of words, so that it will no longer be an unknown word in the results of a spell check Ability to spell check and correct multiple languages
181
For the purposes of this publication, there was also an important non-functional requirement, which is that the service that we use for the spell check must be free and readily available. We also want to use a service that fits in well with the objective of demonstrating how to customize the Management Center; therefore, an externally hosted service will be better than a service that we have to download, deploy, and configure ourselves.
182
11.2 Design
This section provides an overview of the main aspects of the design. The first thing you must do, as explained in 11.2.1, Meeting requirements on page 183, is to decide on what service to use, and which requirements are to be met within the time-scale constraints. For our example, we chose the service from the CDYNE Corporation which we describe in more detail in 11.2.2, CDYNE service detail on page 184. The second design task we had was to decide on the best design that could use the service. This is described in 11.2.3, Component design on page 185.
183
Other requirements, that were pushed out of scope for this book, for example, were the ability to skip or replace a single occurrence of an unknown word. Again, this is due to time constraints and priorities.
184
<?xml version="1.0" encoding="utf-8" ?> <DocumentSummary xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://ws.cdyne.com/"> <MisspelledWord> <Suggestions>web sphere</Suggestions> <word>WebSphere</word> <SuggestionCount>1</SuggestionCount> </MisspelledWord> <MisspelledWord> <Suggestions>e commerce</Suggestions> <Suggestions>commerce</Suggestions> <word>eCommerce</word> <SuggestionCount>2</SuggestionCount> </MisspelledWord> <ver>1.0</ver> <body>WebSphere eCommerce</body> <MisspelledWordCount>2</MisspelledWordCount> </DocumentSummary>
185
of the wcfRichTextPropertyEditor to create the wcfRichTextEditor, which binds a local attribute to the specified property of the model. In this case, that binding would be to the Long Description model property of the product. The binding is bi-directional, so updates to the property are reflected in the display, and edits that you make are propagated to the model property. The wcfRichTextEditor is the widget that gives you the ability to view and edit the property. The model property can be shared so that a number of bound editors can work with the same property. You can also save the model property back to the database using standard features of the Management Center.
creates
Property Editor <class = wcfRichTextPropertyEditor >
edit facility
binds
Model (Product) {long description property} Widget <class = wcfRichTextEditor>
Given that this is how it works, then a design option that we might have thought of taking would be to extend the existing widget or the property editor to add our new behavior.
186
We could not take this approach because these classes are private, and we are not allowed to: Extend them Wrap the objects Construct using them Include buttons within the existing widgets, for example, to invoke the spell checking The decided to create a new Property Editor that binds a button widget to the required model property. When you click the button, a new dialog window opens to spell check the property and to make corrections. You can set up the completed corrections in the model property through the binding, when the user selects the OK button of the dialog. This approach follows the recommended patterns of the Management Center. Figure 11-4 shows that the Spell Check Editors basic pattern is the same as the out-of-the box Property Editor of Figure 11-3 on page 186.
<class = wcfDialog>
creates
displays
opens
binds
Model (Product) {long description property} Widget <class =extSpellCheckButton>
187
With this design, the extPropertySpellCheck class extends wcfPropertyEditor, which is the base class for all Property Editors. This is not a private restricted class because customizations extend this class when developing property editors. The extSpellCheckButton extends the button class, which is a standard component of OpenLaszlo. As previously stated, the widgets of the Management Center are generally in the restricted directory and are private. In our case, we only need the button click to open the new dialog window, which performs the spell check and allows the user to correct the text. For the dialog, we decided to use the wcfDialog class, which was on the recommendation of the Development team who took action to make this class public. We use the Spell Check window object to invoke the spell check service of CDYNE and display the results. When the user clicks the OK button of that window, the correction process ends, and the corrected text is set in the extSpellCheckButtonWidget. Because this widget is bound to the model property, that property gets updated too. It is standard behavior for any model properties that are updated or created to be listed in the active work display as being changed. The user can save all active work at any time, which results in the database being updated with the active work. The process and classes that are needed for the short description property, which is unformatted text, remain the same, which was a design decision that was made because the process is very similar. In practice, the code that was developed for rich text can handle the spell check and correction of the unformatted text. A benefit of this is that it does not matter if the property is rich or unformatted or how large it is because you can easily include the spell check facility by adding a few lines of simple declarative XML to a property panel.
188
Figure 11-5 shows the wire frame for our pop-up window, which is useful for discussions with the customer, and the wire frame helps to define the requirements that are needed to allow the designing and building of the visual prototype.
With suggested corrections 1 title Spell Check There are 2 unknown words 3 highlight A [spellin] misteak or two Unknown word 5 labels spellin Skip 6 skip / replace buttons 2 information 4 unknown word
Replace with
spelling
Replace
Suggestions
9 OK / Cancel Buttons
OK
Cancel
Figure 11-5 Spell check window wire frame, showing suggested corrections
The display areas of Figure 11-5, are all numbered and the following descriptions correspond with those numbers: 1. Title of the window, within the title bar of the window 2. Information text field, which shows: How many unknown words the spell check returned (can show zero) Waiting for spell check service indication All words processed indication Error status message
3. Highlight view shows the full text that was spell checked and with occurrences of the unknown word highlighted. The purpose is to show the unknown word in the context of the sentence. This is to aid correction decisions. With many spell check features, you can use the originating widget to do the highlighting. Because of the restrictions with using the existing widget, see 11.2.3,
189
Component design on page 185, the design approach we used is to handle this within the pop-up window. 4. The current unknown word for the user to process. Information only. Not editable. 5. Labels to explain display areas 4, 7, and 8. 6. A Skip button to accept the unknown word as it is without changes. In this case, the dialog moves on to the next unknown word, if there is one. Also a Replace button to replace all occurrences of the unknown word with the contents of the edit field (7). 7. An edit field that initially shows the unknown word, which you can replace with a selection from the suggestions list (8), or the user can edit the word to correct it. After this field is changed, the Replace button becomes enabled to allow this corrected word to replace the unknown word. 8. The list of suggestions returned in the spell check results. We decided that this will scroll if there were more than 10 suggestions. 9. A Cancel Button, which ends the dialog and no changes occur, at any time. An OK button. As soon as a user makes a change, the button is enabled, and selecting it transfers the changes made using this window to the panel field that holds the property text that is being spell checked and corrected. The corrections area of the display (areas 3 to 8 of the wire frame of Figure 11-5 on page 189), must not display when: There are no unknown words The window is waiting for results There is a service error In other words, the corrections area only displays when there are corrections to the process.
190
We deal with this subject now because you need to know this if you: Want to skip examples and move on to deploy the completed solution. Intend to deploy the example stages that follow on from this section. Want to open and look at the code of the completed solution or the example stages. The zip file contains code for the completed solution and each example stage of the Spell Checker. The files in the zip that you need for the Spell Checker can be described as follows: Directory: LOBTools\WebContent\WEB-INF\src\lzx\redbooks\components File: extSpellChecker.lzx, which contains the final versions of both the pop-up window dialogue panel and the extSpellCheckButton widget class. The following _EXn files are alternatives for the extSpellChecker.lzx class that you can compile in to demonstrate the example development stages that we describe in the progression of the Spell Checker: SpellCheck_EX1_extSpellChecker.lzx SpellCheck_EX2_extSpellChecker.lzx SpellCheck_EX3_extSpellChecker.lzx SpellCheck_EX4_extSpellChecker.lzx SpellCheck_EX5_extSpellChecker.lzx SpellCheck_EX6_extSpellChecker.lzx SpellCheck_EX7_extSpellChecker.lzx
File: extHighlightRichText.lzx, which contains the class extHighlightRichText that is used to highlight the misspelled words in the text that was spell checked. File: extSpellCheckerPropertiesComponent.lzx contains the extPropertySpellCheck class that binds the editor to the model property. File: extUtils.lzx, which contains some utility classes Directory: LOBTools\WebContent\WEB-INF\src\lzx\commerce\catalog\propertiesViews File: CatalogPropertyPane.lzx, which was an out of the box customizable file that we modified to include the Spell Checker editors into the Product Properties Panel. This file also includes some edits that we did for other examples of this book.
191
Directory: LOBTools/src/com/redbooks/commerce/client/lobtools/properties File: CatalogLOB.properties, which we created to contain the properties that we used for the catalog examples of this book, including the Spell Checker. Directory: LOBTools\WebContent\WEB-INF\src\lzx\redbooks\catalog File: extCatalogManagementResourceBundle.lzx, which we created to map the properties that we defined in our new property files to the wcfResourceBundleKey objects needed to let our OpenLaszlo code use the properties. Directory: LOBTools\WebContent\WEB-INF\src\lzx\commerce\catalog File: CatalogExtensionsLibrary.lzx, which was an out the box customizable file that we modified to include the new libraries that we needed to compile. The modifications that we made for the Spell Checker include a commented out section and a listing of each stage example. Deploying the Spell Checker: If you want to deploy the Spell Checker, on its own, you can just deploy the files that we listed from the zip. However, the CatalogPropertyPane.lzx file and the CatalogExtensionsLibrary.lzx file include modifications from other examples of this book. It should be easy enough to identify these, and comment them out if you find compile errors. The comments in these files indicate where we made changes in this book and the examples that they pertain to. Clean and compile the LOBTools project, and restart the server after you deploy the files.
192
Viewing the results of the example stages: By default, the supplied zip compiles to the final completed version of the Spell Checker. To view the results of any of the example stages, modify the CatalogExtensionsLibrary.lzx file, and recompile the LOBTools project. The modification is to replace the following line: <include href="../../redbooks/components/extSpellChecker.lzx" /> Replace the line with the version that you want to compile and view, for example, to view the stage, Example 1, replace the line with: <include href="../../redbooks/components/SpellCheck_EX1_extSpellChecker.lzx" /> The _EXn_ in the filename describes the example stage, where n is the stage number.
Note 3: Whenever you recompile the LOBTools project, we recommend that you clean and build the selected project (LOBTools), which ensures that it is all fully compiled. After this, do not restart the WebSphere Commerce server; instead, start a new browser session, and log into the Management Center, which ensures that the changed LOB tools files are loaded at the browser. For the Spell Checker example, restart the server only if the property file changes are made to ensure that the property file changes are picked up on the server. As an alternative to restarting the server for this, you can refresh the WebSphere Commerce registry. See: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.doc/refs/rmsrefreshregistry.htm
193
2a 2b
Unknown word
2c spellin
spelling spell in spelling spleen spline
Skip
2d
Replace
OK
Cancel
194
The numbered areas of Figure 11-6 on page 194 correspond with the numbers in the following list that describes each area: 1. messageView: This non editable text component contains status messages, such as error states, number of unknown words, and waiting for service response. 2. correctionsView: We use this view to hide or show its sub views, which are required to make corrections. The only time this is set to visible, and hence its sub-views are visible, is when there are corrections to be made. a. highlightedText: This sub-view highlights occurrences of the misspelled word in the source text. For the prototype examples, we just use a richinputtext component. In the later examples, where we implement the functionality for the highlighted text, we replace this with a custom extend version of the richinputtext component. b. labelsView: A sub-view that we use to contain and layout the non-editable text components that show the labels for the widgets of view 2c. This needs to be a separate view, to aid layout, because 2b, 2c, and 2d are spaced along the X axis. c. textView: This sub view contains and lays out the: Non editable text component that shows the unknown word that can be corrected if desired. Edittext component, which allows the user to edit the unknown word so that it can be replaced. List component, which displays the suggested corrections that the user might select and use to replace the unknown word.
d. rightButtonsView: We use this view to contain and layout the Skip and Replace button components. 3. okCancelView: We use this view to contain and layout the OK and Cancel button components. Component types: The component types that we mentioned are all standard OpenLaszlo components. To learn more about these, and other component types, see the components section of: http://www.openlaszlo.org/lps4.1/docs/reference/ After we have a design that has the view hierarchy and the component types to use, we implement the prototype. The declarative XML nature of OpenLaszlo makes this a relatively intuitive task, which you can see from the code that we developed in the Example 11-2 on page 196. The code is mainly a hierarchy of
195
XML that declares the views and components in the same hierarchy as the design of Figure 11-6 on page 194.
Example 11-2 Example 1, view hierarchy code
<!-Some example text that would give the results of the example dataset. The first mispelled word is highlighted in [] and bold --> <attribute name="highlightedTextStr" type="string" value="<b>[e commerce]</b> with WebSphere Commerce" /> <simplelayout axis="y" spacing="10" /> <!-- view at top part of window used to display status messages --> <view name="messageView"> <simplelayout axis="y" spacing="5" /> <text>There were [2] unknown words</text> </view> <!-View containing all correction widgets, hidden when there are no corrections to process --> <view name="correctionsView"> <simplelayout axis="x" spacing="5" /> <!-original text with unknown word highlighted, this is to show it in the context of its sentence --> <richinputtext name="highlightedText" options="ignorelayout" bgcolor="0xdddddd" multiline="true" width="${parent.width}" > <method name="init" > <![CDATA[
196
this.setHTML(true); this.setEnabled(false); this.setText(spellCheckWindow.highlightedTextStr); super.init(); ]]> </method> </richinputtext> <view name="labelView" y="${parent.highlightedText.y +parent.highlightedText.height}" > <simplelayout axis="y" spacing="12" /> <text text="${extCatalogResources.spellCheckUnknownWordLabel.string}" /> <text text="${extCatalogResources.spellCheckReplaceWithLabel.string}" /> <text text="${extCatalogResources.spellCheckSuggestionsLabel.string}" /> </view> <!-- word for correction, correction editing field, and suggestions selection list --> <view name="textView" y="${parent.highlightedText.y +parent.highlightedText.height}" > <simplelayout axis="y" spacing="10" /> <!-- an unknown word returned from the spell check service --> <text>eCommerce</text> <!-edit field for correcting the unknown word, initially populated with unknown word, returned from the spell check service. The user can populate this field with a selected suggestion, or edit the text. --> <edittext>eCommerce</edittext> <!-- the suggestions list, populated with suggestions for the unknown word as returned by the spell check service -->
197
<list id="suggestions_SC" shownitems="10" > <textlistitem>eCommerce</textlistitem> <textlistitem>commerce</textlistitem> </list> </view> <!-- buttons to the right of user correction widgets --> <view name="rightButtonsView" y="${parent.highlightedText.y +parent.highlightedText.height}" > <simplelayout axis="y" spacing="10" /> <!-- skip button: i.e. do not change, the unknown word --> <button id="skipButton_SC" enabled="true" text="${extCatalogResources.spellCheckSkipLabel.string}" > </button> <!-- replace: i.e. replace the unknown word with the word in the corrected word edit field --> <button id="replaceButton_SC" enabled="false" text="${extCatalogResources.spellCheckReplaceLabel.string}" > </button> </view> </view> <!-- The OK and cancel buttons at the bottom of the spell check window --> <view name="okCancelView" placement="footer"> <simplelayout axis="x" /> <button id="applyButton_SC" enabled="false" text="${extCatalogResources.spellCheckDoUpdateLabel.string}" > </button> <button id="cancelButton_SC" enabled="true" text="${extCatalogResources.spellCheckCancelLabel.string}" > <handler name="onclick"> spellCheckWindow.close(); </handler> </button>
198
</view> </wcfDialog> Some points to note about the code in Example 11-2 on page 196 are: The simplelayout components that control the spacing in either the x or y axis. The init method that we declared for the highlightedText object. It overrides the default and gets called on initialization. We needed to set some attribute values here because we cannot declare them in XML in the same way as many of the other components. It is essential here that we call the super init method to ensure that we did not break the standard behavior. A handler is included on the Cancel button that contains the JavaScript to close the window. This handler is associated with the onclick event of the button object that it is declared within. The ${} declarations declare a constraint, for example width={parent.width}. In this case, which occurs for the highlightedText component of the example, the constraint contains the width of the view to the width of the parent view. If the parent width changes, so will the child that has this constraint declared. The ${} are also used to allow property text strings to be substituted, for example, text="${extCatalogResources.spellCheckCancelLabel.string}". This occurs in the example to set the Cancel button label to a property value. This approach is needed because we declared the button in XML, but the text is in a property file that we must dynamically set at a later time. The highlightedText object has its options attribute set, for example, options="ignorelayout". This is because we need this component to span the width of the window by getting it to ignore the simplelayout that is declared for the x axis of its parent view, for example, the correctionsView. For our first prototype, we also include: The new button widget class, extSpellCheckButton, which controls the editing of the property. The new Property Editor class, extSpellCheck, which binds the button widget property to the model property. The modification of the Product Property Panel, which declares the instances of the Property Editor that are needed to create a Spell Checker for the short and long description properties
199
This code is enough to allow the buttons to display in the Property Panel, and to allow the Spell Checker window to display when the users click a button. It is also enough to complete the required code for the Product Property Panel and the Property Editor because they are fairly simple pieces of code. Example 11-3 shows the code for the extSpellCheckButton class. Tracing: The trace code is in the Spell Checker files of the zip that accompanies this book. We removed the trace code from the example code that we show in the spell check code snippets of this book to improve clarity. For a description of how to do tracing see: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.openlazlo.doc/lzx/commerce/foundation/restricted/Logger.lz x/wcfLogger.html
Note: Generally, we follow the good practice of enclosing the JavaScript within methods and handlers with <![CDATA[ ...the JavaScript ... ]]> to avoid compile errors when comments are put in to the JavaScript. The // that we use to declare a comment is seen as invalid XML by the compiler. For short simple methods that have no comments, we did leave the CDATA declarations out. For brevity, we removed the CDATA statements from the Spell Checker code that we show in this book.
Example 11-3 extSpellCheckButton class
<class name="extSpellCheckButton" extends="button"> <attribute name="property" value="null" setter="setProperty(property)" /> <!--Set the {@link wcfModelProperty} to be associated with this spell check. @param wcfModelProperty newProperty: New Property associated with this spell check. --> <method name="setProperty" args="newProperty"> if (typeof(this["property"]) == "undefined" || newProperty != this["property"]) { this.property = newProperty;
200
} </method> <handler name="onclick"> if (this['property']) { spellCheckWindow.open(); } </handler> </class> Some points to note about the code in Example 11-3 on page 200 are: The setProperty method sets a local copy of the property, which is bound to the required model property. Any subsequent changes to the model property are reflected in the value of this local property. Any change to this local property value are reflected back into the model property. For this prototype, the onclick handler has a simple implementation that just opens the spellCheckWindow. We include the code to start the spell check process in the later examples of this book. Example 11-4 shows the new Property Editor class, extSpellCheck, which binds the button widget property to the model property.
Example 11-4 extPropertySpellCheck class
<class name="extPropertySpellCheck" extends="wcfPropertyEditor"> <attribute name="doSpellCheckButtonLabel" value="${extCatalogResources.spellCheckWindowLabel.string}" type="string" /> <!-- create the spell check editor "extSpellCheckButton" for the property --> <method name="createEditor" args="parentComponent"> new extSpellCheckButton(parentComponent, { name: "editor", x: parentComponent.promptWidth + parentComponent.xspacing, property: parentComponent.property, enabled: parentComponent.enabled, text: doSpellCheckButtonLabel }); </method>
201
</class> Some points to note about the code in Example 11-4 on page 201 are: The new property editor class extPropertySpellCheck extends from the standard property editor base class wcfPropertyEditor. The createEditor method, which needs to be implemented by classes that extend wcfPropertyEditor, is responsible for creating the editing widget. In this case, this is an extSpellCheckButton object. The values that are supplied to the new extSpellCheckButton object constructor constrain the position of the button, set an appropriate label for it, and bind the editable property of the widget to the model property. We also change the Product Property Panel code to include and initialize the property editors for the long and short descriptions. Example 11-5 shows the changes we made. Note that some we left some code out to highlight what we changed.
Example 11-5 wcfPropertyGroup class
<class extends="wcfPropertyGroup" groupTitle= "${catalogResources.generalProductInformationSection.string}" name="catManageGeneralProductInformation" open="true" > ... <!-Redbooks customization: Include new wcfPropertyEditor buttons after the short and long description edit fields. These buttons will invoke a spell check and correction window --> <!-- Property: Short Description. A long input box for the short description property. --> <wcfPropertyInputLongText objectPath="CatalogEntryDescription" promptText="${catalogResources.shortDescriptionPrompt.string}" propertyName="sDesc" /> <!-- new button to invoke the spell check
202
on the short description model property --> <extPropertySpellCheck objectPath="CatalogEntryDescription" propertyName="sDesc" />
<!-- Property: Long Description. A rich text editor for the long description property --> <wcfPropertyRichTextEditor objectPath="CatalogEntryDescription" promptText="${catalogResources.longDescriptionPrompt.string}" propertyName="lDesc" /> <!-- new button to invoke the spell check on the long description rich text property --> <extPropertySpellCheck objectPath="CatalogEntryDescription" propertyName="lDesc" /> ... Some important points to note about the code in Example 11-5 on page 202 are: The property panels are declared in the file LOBTools/WebContent/WEB-INF/src/lzx/commerce/catalog/propertiesViews/ CatalogPropertyPane.lzx. We distribute the modified version of this file in the zip file that accompanies this book. Within the file there are a number of property panel instances. The one we modified was for the Product Property Panel because it is for products that we want the buttons to appear. The name="catManageGeneralProductInformation" indicates that this is the panel instance we need to modify. We declared a new instance of our extPropertySpellCheck class just after the short description property editor declaration that was already in the file, which means that our button will appear in the row just below the short description editor, which is what we wanted. We also followed this approach for the long description property. The objectPath and propertyName attributes need to be set respectively to the required values for the model object and model property to allow the Property Editor base behavior to bind the editor to the model property.
203
This is all the code that we need to add to the panel to include the Spell Checker. We did not modify this file in the subsequent examples for the Spell Checker. The Spell Checker can be included for other text properties of this and other Property Panels. To do this, add in declarative code that follows the pattern of Example 11-5 on page 202.
204
4. Click either of the Spell Check buttons to see our prototype panel pop-up, as shown in Figure 11-8 on page 206.
205
At this stage, the text that displays is often hard coded, and in all cases is not relevant to the product of the properties panel. The Cancel button is the only button that is implemented, and clicking that should close the window. Clicking the other Spell Check button should re-open the window again.
206
Note: In OpenLaszlo, the navigation and selection of the XML of a dataset can be set using XPath. For more information, visit: http://www.openlaszlo.org/lps/docs/guide/data-structures.html Example 11-6 shows the code for the dataset. You can see from the example how the wcfDialog had its datapath attribute set to use the dataset as its source for data. Setting it at this level means that all sub-views can use this dataset.
Example 11-6 Static XML dataset
<dataset name="spellCheckDS"> <DocumentSummary xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://ws.cdyne.com/"> <MisspelledWord> <Suggestions>e commerce</Suggestions> <Suggestions>commerce</Suggestions> <word>eCommerce</word> <SuggestionCount>2</SuggestionCount> </MisspelledWord> <MisspelledWord> <Suggestions>web sphere</Suggestions> <word>WebSphere</word> <SuggestionCount>1</SuggestionCount> </MisspelledWord> <ver>1.0</ver> <body>WebSphere eCommerce</body> <MisspelledWordCount>2</MisspelledWordCount> </DocumentSummary> </dataset> <!-- This is the pop up modal window allowing the user to make spelling corrections, there is only one instance of this --> <wcfDialog id="spellCheckWindow" datapath="spellCheckDS:/" width="120" enabled="true" title="${extCatalogResources.spellCheckWindowLabel.string}" visible="false" modal="true" > ... The sub-views are needed to navigate the XML to get to the data they require. To do this, we use the OpenLaszlo approach of using XPath statements to navigate the XML, and bind the subview to the required path within the XML.
207
Example 11-7 shows the snippets of declarative XML that we used to do the navigation and binding. We omitted some code for clarity.
Example 11-7 Xpath navigation and binding
<!-- This is the pop up modal window allowing the user to make spelling corrections, there is only one instance of this --> <wcfDialog id="spellCheckWindow" datapath="spellCheckDS:/" width="120" enabled="true" title="${extCatalogResources.spellCheckWindowLabel.string}" visible="false" modal="true" > ... <!-- View containing all correction widgets, hidden when there are no corrections to process --> <view name="correctionsView" datapath="spellCheckDS:/DocumentSummary/MisspelledWord[1]"> <!-- word for correction, correction editing field, and selection list --> <view name="textView" y="${parent.highlightedText.y +parent.highlightedText.height}"> suggestions
<!-- an unknown word returned from the spell check service --> <text id="wordForCorrection_SC" datapath="word/text()" > </text> <!-- edit field for correcting the unknown word --> <edittext id="correctedWord_SC" datapath="word/text()"> </edittext> <!-- the suggestions list --> <list id="suggestions_SC" shownitems="10" > <textlistitem datapath="Suggestions/text()" /> </list> Some points to note about the code in Example 11-7 are: The correctionsView datapath is set using XPath to navigate down to the first misspelled word element of the XML. This path is then set for all subviews of the corrections view. The wordForCorrection_SC text component navigates further down this path to the word element within the first misspelled word. The body data within this
208
element is bound to the text component, by nature of the declared text() of the XPath, which causes the body text of that element to be displayed. In a similar fashion, the edit field gets bound to, and displays, that same word for correction text. The suggestions list is similarly bound to the suggestions. But in this case, multiple suggestion elements result in multiple list items being displayed. If there are no misspelled words in the XML, then the path for the corrections view evaluates to null; therefore, the corrections view, and its children, are hidden, which is what we want to happen. We demonstrate this behavior in 11.4.5, Explaining the static XML prototype stage (example 3) on page 209.
209
<!-- xml that would be returned from the spell check service if the text to check was "No misspelled words" in this case there are zero misspelled words, resulting in the correction facility being hidden. --> <dataset name="spellCheckDS"> <DocumentSummary xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://ws.cdyne.com/"> <ver>1.0</ver> <body>No misspelled words</body> <MisspelledWordCount>0</MisspelledWordCount> </DocumentSummary> </dataset>
210
<class name="extSpellCheckButton" extends="button"> <attribute name="property" value="null" setter="setProperty(property)" /> ... <!--Set the {@link wcfModelProperty} to represent the spell corrected value. This method is called when the user clicks OK/APPLY to apply the changesfrom the dialog to the model --> <method name="updateText" args="correctedText"> if (this['property']) { this.property.change(correctedText); } </method> <handler name="onclick"> if (this['property']) { spellCheckWindow.doSpellCheck(this,this.property.value); }
211
</handler> </class>
Some points to note about the code in Example 11-9 on page 211 are: We changed the onclick handler so that it calls the new doSpellCheck method of the spell check window. It passes in: A reference to this instance of the extSpellCheckButton, which the spell check window needs to return the corrected text. The text to be spell checked, from the property. The updateText method uses the change method for the property, which causes the model property to be updated so that it can be saved back to the database. Example 11-10 highlights the main code changes that we made to the spell check window wcfDialog object.
Example 11-10 Example 4, spellCheckWindow code
<wcfDialog id="spellCheckWindow" datapath="spellCheckDS:/" width="120" enabled="true" title="${extCatalogResources.spellCheckWindowLabel.string}" visible="false" modal="true" > <!-- formattedText is the text, that gets corrected and returned, for unfromatted text this gets set to the same as the textToCheck parameter --> <attribute name="formattedText" type="string" /> <!-- the source object which contains the actual editor that we return the corrected text to --> <attribute name="sourceObjectToCorrectSpelling" /> .... <!-- Do a spell check on the supplied "newTextToCheck" using the spell check window and service. Note: newTextToCheck can be with or without formatting (html). The supplied sourceObj must implement method "updateText" args="correctedText". This is used by this spell checker to apply the corrected text when the user clicks the OK button after making corrections.
212
Note: this example version is incomplete, it just logs the text to check for testing purposes --> <method name="doSpellCheck" args="sourceObj, newTextToCheck"> if (wcfLogger.enabled) wcfLogger.log( "com.redbooks.components", "INFO", "spellCheckWindow", "doSpellCheck" ,"Text to check is:" +newTextToCheck); // to keep compatability with formatted text spell checking, // the check is always done using unformatted text, but // corrections are always applied to // the this.formattedText attribute this.formattedText = newTextToCheck; this.sourceObjectToCorrectSpelling = sourceObj; this.open(); </method> .... </wcfDialog> Some points to note about the code in Example 11-10 on page 212 are: The doSpellCheck method saves the supplied arguments to some new attributes of the wcfDialog spell check window object. The doSpellCheck method uses wcfLogger to output a trace statement to test the input text string that is being spell checked. The strategy we had was to save the text being spell checked in its formatted form and to make corrections by string replacement. Before the text was sent to the spell checker service, or used in the highlighted unknown words view, the formatting html would be removed. If the input text, to spell check, did not have any formatting, then this strategy would still work.
213
To view example 4: 1. Recompile for example 4, as explained in Note 2 of 11.3, Deploying the complete or staged examples on page 190. 2. Log into the Management Center with a new browser session, but this time ensure that you have the following debug parameters on the end of the URL: https://localhost:8000/lobtools/cmc/ManagementCenter?logger.display= true&logger.target=debugger&logger.components=com.redbooks.component s This is to ensure that you can trace our Redbooks components. For a detailed description of debugging, see 5.3, Debugging on page 74. 3. Use the Management Center to open a product properties (product details) page. At this point, we want to enable the tracing of our Spell Checker. Select the top bar menu option: Management Center Logging and Tracing. At this point, you should see the logging and tracing options window and be able to select the following items, as shown in Figure 11-10 on page 215: Send Immediately com.redbooks.compoonents = ALL After you select the options, click the Enable button of that window to enable the tracing.
214
4. Click any of the Spell Check buttons to view the spell check window, as shown in Figure 11-9 on page 210. 5. Look in the debug window to see text similar to the following: Thu Aug 14 13:26:17 GMT+0100 2008 com.redbooks.components FINER extSpellCheckButton doSpellCheck ENTRY Thu Aug 14 13:26:17 GMT+0100 2008 com.redbooks.components FINER spellCheckWindow doSpellCheck ENTRY Thu Aug 14 13:26:17 GMT+0100 2008 com.redbooks.components INFO spellCheckWindow doSpellCheck Text to check is:A cute giraffe print. Thu Aug 14 13:26:17 GMT+0100 2008 com.redbooks.components FINER spellCheckWindow doSpellCheck RETURN Thu Aug 14 13:26:17 GMT+0100 2008 com.redbooks.components FINER extSpellCheckButton doSpellCheck RETURN The text in bold confirms that the text to spell check correctly passed through to the spell check window. In your case, the debug text of A cute giraffe print, should match the source text of the property that you selected to spell check.
215
<!-- Dataset using, cdyne service as an example spell check service http://ws.cdyne.com/SpellChecker/check.asmx/CheckTextBody? LicenseKey=anything&BodyText=string note: we set the changeable BodyText parameter dynamically, as this is the text to spell check. @see http://ws.cdyne.com/SpellChecker. Timeout set to 15 secs - default is 30, which is a bit long. --> <dataset name="spellCheckDS" type="http" acceptencodings="true" request="false" querytype="put" timeout="15000" trimwhitespace="true" src="http://ws.cdyne.com/SpellChecker/check.asmx/CheckTextBody" ondata = "spellCheckWindow.handleData(this);" onerror = "spellCheckWindow.handleError(this);" ontimeout = "spellCheckWindow.handleTimeout(this);" />
216
Some points to note about the code of Example 11-11 on page 216 are: The request attribute was set to false to allow the request for the service to be invoked when the doSpellCheck method runs. The time-out was reduced to 15 seconds. The three handlers, for data, error and time-out were set to be methods of the spell check window wcfDialog object. This was to simplify and centralize this code. Example 11-12 shows how we modified the doSpellCheck method to use a new utility method to remove any HTML formatting from the text to spell check. It then goes on to call a new method, callSpellCheckService, which: Sets the word that is to be spell checked as a query parameter for the service. Actions the service by calling the doRequest method on the dataset.
Example 11-12 Using the CDYNE service
<wcfDialog id="spellCheckWindow" datapath="spellCheckDS:/" width="120" enabled="true" title="${extCatalogResources.spellCheckWindowLabel.string}" visible="false" modal="true" > ...... <attribute name="textToCheck" value="" type="string" /> ....... <method name="doSpellCheck" args="sourceObj, newTextToCheck"> this.formattedText = newTextToCheck; this.sourceObjectToCorrectSpelling = sourceObj; this.textToCheck = extStringUtil.removeTags(newTextToCheck); // hide any previous results in the corrections view, for now this.correctionsView.setAttribute("visible", false); this.setWaitForServiceMessage(); this.open(); this.callSpellCheckService(); </method> <!-- use the spell check service --> <method name="callSpellCheckService" > spellCheckDS.setQueryParam("BodyText", this.textToCheck); spellCheckDS.setQueryParam("LicenseKey", "anything");
217
spellCheckDS.doRequest(); </method> For this example, we also made some changes that deal with the status messages and word count. We changed the way we implement that in the later examples, so we defer describing how that was done for now.
Shortly after this, the spell check service should return an XML result declaring that there are no unknown words. At this point, the window changes to display, as shown in Figure 11-9 on page 210.
218
5. Click the Cancel button to close the window, and then edit the short description field that is on display in the property panel to introduce spelling errors in two words. 6. Click the spell check button for the short description, and results that are similar to Figure 11-12 are displayed. The unknown word that is displayed should match your first misspelled word of the short description. The number of suggestions that are displayed vary depending on what the misspelled word is.
7. Repeat the tests for the long description property, and you should receive similar results.
219
We extend the standard OpenLaszlo richinput text widget to provide the required functionality. Example 11-13 is from the extHighlightRichText file, and it shows our actions.
Example 11-13 New highlighting component
<class name="extHighlightRichText" extends="richinputtext"> <!-- html used to highlight text in [] bold and red, can be changed if required --> <attribute name="prependHighlightHTML" value="<b><font color="#FF0000">[" type="string" /> <attribute name="appendHighlightHTML" value="]</font></b>" type="string" /> <method name="init" > this.setHTML(true); this.setEnabled(false); super.init(); </method> <!-- highlight all occurrences of the supplied "wordToHighlight" in the text of this widget, refereshes display --> <method name="highlightWord" args="wordToHighlight"> if(! (extStringUtil.isBlankString(this.text) && extStringUtil.isBlankString(wordToHighlight) ) ) { // first, remove highlighting that may be there from previous var cleanedSentences = wcfStringUtil.replace(this.text, this.appendHighlightHTML, ""); var cleanedSentences = wcfStringUtil.replace(cleanedSentences, this.prependHighlightHTML, ""); // finally, highlight occurences of the word var highlightedWordStr = this.prependHighlightHTML + wordToHighlight +this.appendHighlightHTML; var result = wcfStringUtil.replace(cleanedSentences, wordToHighlight, highlightedWordStr); this.setAttribute("text", result); }
220
</method> <!-- replace all occurrences of the supplied "wordToChange" with "newWord" in the text of this widget --> <method name="replaceWord" args="wordToChange, newWord"> // replace the word so that it won't show uncorrected in // highlighted text this.text = wcfStringUtil.replace(this.text, wordToChange, newWord); </method> <!-- override of the normal datapath apply data method, this is to use the word returned from the datapath to highlight the text in the widget --> <method name="applyData"> highlightWord(this.data); </method> </class> Some points to note about the code in Example 11-10 on page 212 are: The method, highlightWord, highlights the specified word in the display text of this object. It first removes any existing highlighting, and then replaces all occurrences of the word with a highlighted version of that word. There are local strings to control the style of the highlighting, which you can change to give a different style, if required. You can use the method, replaceWord, to replace a word in the text of this object. We need this in cases where there are a number of different words that are being corrected. The method clears a previous replaced word so that it is not highlighted after it is replaced. The applyData method is a standard OpenLaszlo method that is called when the datapath has new data. Normally, the richinputtext class that we extended uses the new data to set the display text. Our strategy, though, is to use the data from the datapath as the word to highlight. By overriding the applyData method we can do that.
221
Note: When we coded this example, we just invoked the highlightWord method from within a procedure. We decided later that the applyData approach is better, and we used it in the final solution, as we describe in 11.4.13, Explaining the final stage on page 223. In order to use the new highlighting widget, we change the spell check window wcfDialog object code, as shown in the code snippet in Example 11-14.
Example 11-14 Using the highlighting component
<!-- original text with unknown word highlighted, this is to show it in the context of its sentence --> <extHighlightRichText name="highlightedText" options="ignorelayout" bgcolor="0xdddddd" multiline="true" width="${parent.width}"> </extHighlightRichText> ..... <!-- an unknown word returned from the spell check service --> <text id="wordForCorrection_SC" datapath="word/text()" > <handler name="ondata"> spellCheckWindow.correctionsView.highlightedText.setText(spellCheckWind ow.textToCheck); spellCheckWindow.correctionsView.highlightedText.highlightWord(this.dat a); </handler> </text> Some points to note about the code in Example 11-14 are: We changed the richinputtext widget declaration to declare our new widget type, extHighlightRichText. When the data loads into the displayed word for correction, we set the text to display for the widget to the unformatted text to check value. We then set the highlighted word to be the word for correction.
222
To view example 6: 1. Recompile for example 6, as we explained in 11.4.2, Viewing the visual prototype stage (example 1) on page 204. 2. Log into the Management Center with a new browser session, and open a Product Properties (product details) page. 3. Repeat the tests from 11.4.10, Viewing the CDYNE service prototype stage (example 5) on page 218, where you tested with misspelled words, but this time, the displayed spell check window should show the highlighting, similar to Figure 11-13. Occurrences of the unknown word are highlighted in red and are bracketed.
223
much dynamic event driven code and the JavaScript that is needed to implement that. To help maintenance, we include a controller object to help centralize and control the states and transitions of the correction process. Figure 11-14 shows the states and transitions of the process. The ellipses are the states and the connections are the transitions.
call service checking time out cancel error result[no unknown words]
cancel
skip/ replace
error cancel
correctionsToDo
skip/ replace
wordEdited
Using the states and transitions from the diagram as a guide, we implement our state controller object, as shown in Example 11-15.
Example 11-15 State controller
<!-- Single Object to record state and control the state transitions of the Spell Check Process. Changes to the currentState attribute will trigger associated listeners and constraints. --> <node id="spellCheckerController_SC"> <attribute name="state" type="string" value="initial" /> <attribute name="transition" type="string" />
224
<!-- Flags to indicate if a user has replaced a word using the spell check window --> <attribute name="wordReplaced" type="boolean" value="false" /> <handler name="ontransition" args="theTransition"> switch (theTransition) { case "to initial" : this.setAttribute("state", "initial"); this.setAttribute("wordReplaced", false); break; case "initial to checking" : this.setAttribute("state", "checking"); break; case "checking to correctionsToDo" : this.setAttribute("state", "correctionsToDo"); break; case "checking to noCorrectionsNeeded" : this.setAttribute("state", "noCorrectionsNeeded"); break; case "checking to error" : this.setAttribute("state", "error"); break; case "checking to timedOut" : this.setAttribute("state", "timedOut"); break; case "correctionsToDo to wordEdited" : this.setAttribute("state", "wordEdited"); break; case "to correctionsToDo" : this.setAttribute("state", "correctionsToDo"); break; case "to allProcessed" : this.setAttribute("state", "allProcessed"); break; default: // error due to undefined transition request // {programming error} if (wcfLogger.enabled) wcfLogger.log("com.redbooks.components", "ERROR", "spellCheckerController_SC", "ontransition", "undefined transition request was: " +this.transition); break; }
225
if (wcfLogger.enabled) wcfLogger.log("com.redbooks.components", "INFO", "spellCheckerController_SC", "ontransition", "transition = " +this.transition +" , new state = " +this.state); </handler> </node> Some points to note about the code in Example 11-15 on page 224 are: The state attribute records the current state of the spell check and correction process. You can easily use this attribute in constraints to control what needs to happen to other objects when the state changes. Example 11-16 shows how this is achieved. A transition attribute is declared, which the spell check code can set whenever a transition is needed. A handler is listening for changes to the transition attribute, and it recognizes the transition that is requested and moves to the appropriate state. Benefits of the state controller object: Using our state controller object: Simplified the spell checker code Improved readability Helped reduce the number of programmatic errors Eased our aim of separating declarative XML from JavaScript code Initially, we built the spell checker without it, then refactored the code to introduce it. As an illustration of how the spell check window code can use the state, look at the case of the messageView, which notifies the user of the status of the spell checking process, as shown in Example 11-16.
Example 11-16 Using the state controller
<wcfDialog id="spellCheckWindow" datapath="spellCheckDS:/" width="120" enabled="true" title="${extCatalogResources.spellCheckWindowLabel.string}" visible="false" modal="true" > ..... <!-- view at top part of window used to display status messages, message depends on state -->
226
<view name="messageView"> <simplelayout axis="y" spacing="5" /> <state apply="${spellCheckerController_SC.state == 'checking'}"> <text text= "${extCatalogResources.spellCheckWaitingMessage.string}" /> </state> <state apply="${spellCheckerController_SC.state == 'correctionsToDo'}"> <text text="${spellCheckWindow.spellCheckResultsMsg}" /> </state> <state apply="${spellCheckerController_SC.state == 'noCorrectionsNeeded'}"> <text text="${spellCheckWindow.spellCheckResultsMsg}" /> </state> <state apply="${spellCheckerController_SC.state == 'error'}"> <text text= "${extCatalogResources.spellCheckErrorGettingResultsMsg.string}" /> </state> <state apply="${spellCheckerController_SC.state == 'timedOut'}"> <text text= "${extCatalogResources.spellCheckTimedOutMsg.string}" /> </state> <state apply="${spellCheckerController_SC.state == 'allProcessed'}"> <text text= "${extCatalogResources.spellCheckAllDoneMsg.string}" /> </state> </view> ... Some points to note about the code in Example 11-16 on page 226 are: We use state declarations for each state where we have a status information message to show. When a state apply condition is true, the declarations that are inside of those state tags will apply. Within each state, we declare a text component that contains resource bundle text that displays the applicable text message for that state.
227
The state tags that are shown effectively form the equivalent of a case statement where only one text component at a time displays text in the information area of the window, You might have noticed that the status information messages for no unknown words, and a number of unknown words, contains a word count message, for example, There are [2] unknown words. The unknown word count is returned in the XML message that the CDYNE service returns, which means that for two of the state sections of the Example 11-16 on page 226, we use a dynamically built attribute string spellCheckWindow.spellCheckResultsMsg rather than just a static resource bundle property. Example 11-17 shows how we built this dynamic message.
Example 11-17 Building the word count message
<wcfDialog id="spellCheckWindow" datapath="spellCheckDS:/" width="120" enabled="true" title="${extCatalogResources.spellCheckWindowLabel.string}" visible="false" modal="true" > ..... <!-- the number of unknown words returned from the spell check service --> <text id="unknownWordCount_SC" visible="false" datapath= "spellCheckDS:/DocumentSummary/MisspelledWordCount/text()" ondata="spellCheckWindow.handleUnknownWordCount(this.data);" /> <!-- The spell check results prompt message with [count] place holder for the unknown word count --> <attribute name="spellCheckResultsTemplate" value="${extCatalogResources.spellCheckSuggestionsPrompt.string}" type="string" /> <!-- The spell check results prompt message with the unknown word count --> <attribute name="spellCheckResultsMsg" value="${extCatalogResources.spellCheckSuggestionsPrompt.string}" type="string" /> <!-- if no unknown words set controller state to no corrections to
228
do, otherwise set for corrections to do --> <method name="handleUnknownWordCount" args="count" > // first, replace the count placeholder with the count // in the prompt message this.spellCheckResultsMsg = wcfStringUtil.replace(this.spellCheckResultsTemplate , "[count]", count); // next, set the state, this event will update the message // view with the prompt message if (count!="0") { spellCheckerController_SC.setAttribute("transition", "checking to correctionsToDo"); } else { spellCheckerController_SC.setAttribute("transition", "checking to noCorrectionsNeeded"); } </method> ... Some points to note about the code in Example 11-17 on page 228 are: We added a text component with an id of unknownWordCount_SC to hold the unknown word count. The datapath setting of this causes it to be populated with the misspelled word count that the CDYNE service returns. We set the ondata event of this text component to call a new method, handleUnknownWordCount, which: Replaces the place holder in the template message string with the unknown word count Sets the result of this into the spellCheckResultsMsg attribute that is used Sets the appropriate transition for our state controller. If the count is not zero, it is set to "checking to correctionsToDo"; otherwise, it is set to "checking to noCorrectionsNeeded". The transition setting then triggers the use of the dynamically formed string as the status message, as shown in Example 11-16 on page 226. Next, we explain how we mapped button states to the process states. We take into account the states in the diagram of Figure 11-14 on page 224, and then consider how the states affect the buttons.
229
Replace
state = wordEdited (for example, a word is selected or edited, but no other action is taken yet) wordReplaced = true (for example, the replace button is used to replace a word)
OK
wordReplaced = false
Note: We include a flag in our state controller for the wordReplaced state because we need to enable the OK button as soon as a first word is replaced, but we do not want to disable it until the user subsequently presses OK or the user ends the process with skip or cancel. We have no single state that achieves this. We show some snippets of the code to implement the button logic in Example 11-18. The constraints that enable or disable the Apply and Replace buttons, based on our controllers state, are shown in bold. Methods of the spell check widow are bound to the onclick events of the Skip, Replace, and Apply buttons. This approach helps us to reduce the quantity of the JavaScript code that was mixed in with the declarative XML.
Example 11-18 Button constraints
.... <!-- skip button: i.e. do not change, the unknown word --> <button id="skipButton_SC" text="${extCatalogResources.spellCheckSkipLabel.string}" onclick="spellCheckWindow.handleSkip();" /> <!-- replace: i.e. replace the unknown word --> <button id="replaceButton_SC" enabled="${spellCheckerController_SC.state == 'wordEdited'}" text="${extCatalogResources.spellCheckReplaceLabel.string}" onclick="spellCheckWindow.handleReplace();"
230
/> .... <button id="applyButton_SC" enabled="${spellCheckerController_SC.wordReplaced}" text="${extCatalogResources.spellCheckDoUpdateLabel.string}" onclick="spellCheckWindow.handleApply();" /> <button id="cancelButton_SC" enabled="true" text="${extCatalogResources.spellCheckCancelLabel.string}" > <handler name="onclick"> spellCheckWindow.close(); spellCheckerController_SC.setAttribute ("transition", "to initial"); </handler> </button>
We also implemented the logic around selecting and editing the current unknown word. Example 11-19 shows snippets of code that we used for that.
Example 11-19 Suggestion selection and edit logic
... <!-- an unknown word returned from the spell check service --> <text id="wordForCorrection_SC" datapath="word/text()" /> <!-- edit field for correcting the unknown word, initially populated with unknown word, returned from the spell check service. The user can populate this field with a selected suggestion, or edit the text. --> <edittext id="correctedWord_SC" datapath="word/text()" onchanged="spellCheckWindow.handleUnknownWordEdited(this.text);" /> <!-- the suggestions list, populated with suggestions for the unknown word, as returned by the spell check service --> <list id="suggestions_SC" shownitems="10" onselect=
231
"spellCheckWindow.handleSelectedSuggestion(this.getText());" > <textlistitem datapath="Suggestions/text()" /> </list> ... <!-- handle the case where a user has edited the word for correction i.e. enable the replace button and set the controller state --> <method name="handleUnknownWordEdited" args="editedWord"> if((! extStringUtil.isBlankString(editedWord))) { spellCheckerController_SC.setAttribute("transition", "correctionsToDo to wordEdited"); } </method> <!-- when the user selects a suggestions, replace the displayed corrected word with it. Also, enable the replace button, as a suggestion has been selected --> <method name="handleSelectedSuggestion" args="selectedWord"> if (! extStringUtil.isBlankString(selectedWord)) { correctedWord_SC.setText(selectedWord); spellCheckerController_SC.setAttribute("transition", "correctionsToDo to wordEdited"); } </method> Some points to note about the code in Example 11-19 on page 231 are: The user can edit the editable text field that is populated with the current unknown word. We bound the handleUnknownWordEdited method to its onchanged event so that this method is invoked when the user makes any kind of edit to that field. The handleUnknownWordEdited method sets our state controller transition so that the state moves on to wordEdited, which triggers activity that is bound to that particular state, for example, the Replace button becomes enabled. We took a similar approach for the suggestion list, but in this case we use the onselect event to invoke the handleSelectedSuggestion method. The handleUnknownWordEdited method also sets our state controller transition so that the state moves on to wordEdited. A selection of a suggestion moves the process onto the same state as though the user did a
232
manual edit. In this case though, the corrected word edit field is set to the suggestion. The previous examples of the spell checker only use the first unknown word that the CDYNE service returns because the datapath is set to use the first occurrence of unknown word in the XML. To complete the spell checker implementation, we moved to the next occurrence of unknown word in the XML. To do this, we added a new moveToNextWord method to the spell check window object, as shown in Example 11-20.
Example 11-20 Move to next unknown word snippet
... <!-- moves the display on to the next misspelled word in the retrieved xml --> <method name="moveToNextWord"> var dp = this.correctionsView.datapath; if (dp.selectNext()) { // also need to check that we are still iterating through // Misspelled Word Elements. // there are other element types at this level var elementName = dp.getNodeName(); if (elementName == "MisspelledWord") { spellCheckerController_SC.setAttribute("transition", "to correctionsToDo"); this.correctionsView.applyData(); } else { // hide corrections as all processed now this.correctionsView.setAttribute("visibility", "hidden"); spellCheckerController_SC.setAttribute("transition", "to allProcessed"); } } else { // hide corrections as all processed now this.correctionsView.setAttribute("visibility", "hidden"); spellCheckerController_SC.setAttribute("transition", "to allProcessed"); } </method> ...
233
Some points to note about the code in Example 11-20 on page 233 are: We moved to the next node by: Getting the datapath of the corrections view, which is initially set to the first unknown word node of the XML. Using the selectNext method of the datapath to move to the next node. We found that we could not just use the fact that there were no more nodes to determine when all unknown words were processed because there were other types of nodes at the same level of the XML. The iterations were passing down through those nodes too, which we fixed by adding in a check to make sure that the next node that was found was of the unknown word element type. Moving the datapath on to the next unknown word causes the child views of the corrections view to update, which is based on the relative XPath settings used for the child views. When all corrections are processed, the state is transitioned to allProcessed. Note: We used this section to explain some important points about how we designed and implemented the spell checker. To understand the full solution, you can look through the complete files of the zip that accompanies this book. The files that we use for the spell checker are described in 11.4.2, Viewing the visual prototype stage (example 1) on page 204.
234
You can now fully test the Spell Checker. Some things that we tested, that you might want to check are: Try out the Spell Checker for the rich text - long description and the short text unformatted text properties using cases of: Various misspelled words Same word misspelled a number of times No misspelled words Various numbers of misspelled words
The Replace button is only enabled after a suggested correction is selected or the unknown word is edited The OK button is only enabled after a word is replaced The highlight text area highlights all occurrences of the current unknown The Skip button skips to the next unknown word, if there is one Disconnect from the internet to see that error message Persistence: If the user clicks the OK button, the changed product should be listed in the active work section to the left of the window. Clicking the save button should persist the spell correction changes that you made, which you can confirm by: logging out, starting a new browser session, and opening the Product Properties panel for the product that was modified. The changes made should be seen.
11.5.1 Dictionary
In this section, we discuss why a dictionary is a useful feature to have for the spell checker, and then we provide an overview of some ways that you can implement it. A dictionary feature allows users to get the Spell Checker to stop reporting particular spellings as unknown words. In the eCommerce arena, this is particularly useful because things, such as brand names and domain specific terms, are often used and might be unknown to the Spell Checker.
235
The obvious approach, is to use a service that has a dictionary. We explore using a different service further in 11.5.3, Using a different service on page 237. Another approach is to save and reuse a list of known words, perhaps in the WebSphere Commerce database. There are various ways that you can achieve this persistence. The current Spell Checker solution needs modifying to: Provide an add to the dictionary button to persist the current unknown word to the list of known words. Retrieve the list of known words and strip occurrences of them from the text that is sent to the Spell Check service for spell checking.
236
11.6 Summary
In this section, we summarize what we covered with the Spell Checker, including, the challenges we had and what was good. We also provide some tips. The main challenges we had were: Finding an appropriate service. This proved to be harder than expected. This situation might change in the future as more service providers appear and there are less restrictions on usage. Working out the best approach. We spent a lot of time examining alternatives before we decided on using a new property editor, which was the only way to avoid issues with wanting to use the out of the box Management Center widgets.
237
The main things that we liked were: OpenLaszlo. It has the following features that we found to be particularly elegant and powerful: The declarative nature makes it easy to assemble the look and feel prototype This prototype can be taken forward with the final solution having similar construction code into that of the original prototype Ease of use of datasets for local static data and with minimal change for an external RESTful service Use of XPath to navigate the XML datasets, which is easy to understand and do The way that the viewable components can easily be bound to the XML of the dataset, which makes it easy to move from a static prototype to the use of dynamic data The ease with which events can be listened for and acted on using constraints The architecture of the Management Center made it easy to introduce a new property editor. It is easy to include a property editor in property panels. It should also be relatively easy to add a grid property editor. The main tips we want to summarize are: Ensure that you follow the Management Center guidelines for customizing the Management Center Take small steps at a time, and build up from the look and feel prototype to the final solution. The less experience you have with Management Center customization, the smaller the steps you should take. Experiment with OpenLaszlo. Some of this can be done offline from WebSphere Commerce. One way to do this, is to use the OpenLaszlo site, where the documentation contains live editors that you can use to try out code snippets. For an example of this, go to the OpenLaszlo reference documentation, select a component, and then select the Edit button in one of the example panels. The reference documentation for version 4.1 is at: http://www.openlaszlo.org/lps4.1/docs/reference/# Use declarative XML in preference to JavaScript Use JavaScript, and try to keep the bulk of JavaScript separate of the declarative XML.
238
12
Chapter 12.
239
240
On a federated environment with multiple nodes, the changes of the stores flow files must be somehow distributed to all nodes. While you can enable staging support for the Custom Application Configuration Management tool, you cannot enable staging support for store flow. The store flow settings offer only the selection of predefined values. The setting management is limited to disable/enable a specific setting, or to choice between a list of values. No data can be entered. No new settings can be added. Accessing the store flow settings is based on boolean value. The <flow> tag library only allows you to check if a specific setting is enabled or disabled. The store flow feature supports Extended Sites but the Custom Application Configuration Management tool does not. Another approach is to misuse Content Spots to hold configuration settings, which might be a quick solution for very small applications but we do not recommend it at all because configuration settings would get mixed up with marketing content, and there is no possibility to protect configuration settings from changing by non-entitled users.
241
Utilities View
Properties View
The following UI elements are specific to the Custom Application Configuration Management tool: Explorer view The Explorer view allows you to navigate through the configuration setting categories with which you want to work. Select a category to load all of its containing configuration settings that are in the List view. The Explorer view always includes the Search Results node (to allow you to return to a list-of-results at any time) and Active Work node (to show the configuration settings that you currently have open). List view The view is displayed in the main work area. The list view lists configuration settings in the form of a table. You can view the list and select a specific row to see more details. You can also reorder, hide, or show columns.
242
Properties view Also displayed in the main work area, the properties view shows details about the selected configuration setting, which you can also edit in this view. You access the properties view from either selecting to create a new instance of a configuration setting or by selecting an existing configuration setting from a list view. Utilities view In the utilities view, you can locate and use configuration settings that you want to reference without navigating away from the current view. Use the utilities view to search for configuration settings. Browse and clipboard features are also provided. The customization work is comprised of the following high-level steps: Customize the Management Center user interface: Define the main tool definition. Define the OpenLaszlo classes that are instantiated in the tool definition class. Define the UI text and images for the Management Center objects. Define the library class for all OpenLaszlo classes. Add the new tool to the Management Center menu. Authorize the new tool to the business role. Customize the WebSphere Commerce services A new service module called CustomAppProperties is created: Define the CustomAppProperties noun. Generate the CustomAppProperties service module projects. Generate the logical Service Data Objects (SDOs) for the CustomAppProperties noun. Implement the Persistence layer for the WebSphere Commerce BOD programming model. Implement Business Object Mediators to transform logical SDOs and physical SDOs. Configure the Data Service layer for the CustomAppProperties service module. Implement the Business Logic layer. Implement the client library. Implement access control.
243
Deploy and validate the CustomAppProperties service module with JUnit. Customize the Management Center Web application: Define retrieval services for the new Management Center object. Define process services for the new Management Center object. Figure 12-2 shows the Project Explorer, which outlines all projects that are involved in the Custom Application Configuration Management tool scenario.
You work on the following projects: Enterprise Applications WebSphere Commerce This project contains the asset definition of the WebSphere Commerce Enterprise Application Archive (EAR) that you must execute in the J2EE environment of the IBM WebSphere Application Server. This project is a default WebSphere Commerce Developer project, and you will update its configuration.
244
EJB Projects CustomAppProperties-Server This project contains all BOD commands, Business Object Mediators, and physical SDOs. It implements the component facade. You generate this project during customization. You change the generated assets and update the projects configuration. Dynamic Web Projects CustomAppPropertiesServicesHTTPInterface This project serves as a remote access proxy and contains a Web services router servlet for the CustomAppProperties component. You generate this project during customization. No updates on assets or configuration are required. LOBTools This project contains all assets of the Management Center user interface and Management Center Web application. This project is a default WebSphere Commerce Developer project and is created when you enable the Management Center feature from Feature Pack 3.0.1. You create and update several assets, and you update the projects configuration. WebServicesRouter This project is intended to define all of the Web services that your WebSphere Commerce instance provides, which only applies to Web services using the functional architecture of WebSphere Commerce prior to Feature Pack 2. Therefore, this project is not used with Management Center, so we do not use it in this scenario. This project is a default WebSphere Commerce Developer project. You only need to copy assets from this project to others. Other Projects ComponentProjects This project stores the ComponentProjects design pattern. Together with the Design Pattern Toolkit (DPTK), these assets generate WebSphere Commerce services. All CustomAppProperties projects are generated from this toolkit.
245
This project is a default WebSphere Commerce Developer project, but you have to download and import this project into your workspace before you use it. You create a few assets, and then start the generation process. CustomAppProperties-Client This project contains the service client library that acts as a facade to the CustomAppProperties service. You generate this project during customization. You do not need to make any changes to the assets, but you do update the projects configuration. CustomAppProperties-DataObjects This project contains the logical Service Data Objects, represented as Java classes, the SDO data definition (XML Schema Definition - XSD), and the SDO service definition (Web Services Description Language WSDL). Both CustomAppProperties-Server and CustomAppProperties-Client projects access this project and share the same assets. The reason to out source the logical SDOs is to keep <service>-Client and <service>-Server projects independent without adding redundancy on the code. You generate this project during the customization, make changes to the assets, and update the projects configuration. CustomAppProperties-UnitTests This project contains JUnit-based unit tests to test the CustomAppProperties service operations. This project utilizes CustomAppProperties-Client and CustomAppProperties-DataObjects projects and is configured to access CustomAppProperties service by a Web service using HTTP. You generate this project during the customization, make changes to the assets, and update the projects configuration.
246
WebSphere Commerce services in WebSphere Portal applications, the IBM Sales Center, the Management Center, and any other custom applications. The Management Center and a JUnit test application use the CustomAppProperties service that is built within this scenario. Figure 12-3 shows an overview of the main building blocks of the Custom Application Configuration Management tool. The Management Center, WebSphere Commerce server, and CustomAppProp service test application are independent, not necessarily sharing a common environment and connected through HTTP-based interfaces. However, the customization steps that we describe in this chapter are all performed on your local workstation, running WebSphere Commerce Developer.
OpenLaszlo Application
Logical SDOs
BOD Commands
Logical SDOs
SOAP Request EJB Call
Client Library
CustomAppProperties-Client
JDBC
Database Server
Legend
Figure 12-3 Architectural overview diagram of the Custom Application Configuration Management tool
The Management Center uses name-value-pair URLs to invoke services on the WebSphere Commerce server, through the mediation of the Management Center Web application. Returning documents are of simple-formatted XML.
247
The CustomAppProperties service test application uses OAGIS messaging. Instead of the name-value-pair URLs that represent a typical Web request, business components declare structured objects that represent services. The components use OAGIS XSDs to define services. Technically, the communication is performed by Web services using the Simple Object Access Protocol (SOAP). The applications that run inside of the WebSphere Commerce server employ local Enterprise Java Beans (EJBs) to perform fast and container-managed communication, which includes transaction handling. The Data Access Service (DAS), which the Data Service layer provides accesses the WebSphere Commerce database.
248
249
Example 12-1 Tool definition for the Custom Application Configuration Management tool
<class name="extConfigManagement" extends="wcfBusinessObjectEditor" > <!-- Filter definitions --> <extConfigCategoryFilter /> <!-- Primary object definitions --> <extConfigTopObjectDefinition /> <extConfigOrganizationalObjectDefinition/> <extConfigPropertyPrimaryObjectDefinition /> <extConfigCategoryPrimaryObjectDefinition /> <!-- Search definitions --> <extFindConfigurations /> </class>
250
<class name="extConfigOrganizationalObjectDefinition" extends="wcfOrganizationalObjectDefinition" objectType="ConfigurationTop" organizedObjectTypes="ConfigCategory" icon="configTreeFolderIcon" displayName="${extConfigResource.configTopDisplayName.string}"> <wcfGetChildrenService url="/cmc/GetConfigCategories"> <wcfServiceParam name="storeId"/> </wcfGetChildrenService> </class> extConfigTopObjectDefinition: Top object definition for the Custom Application Configuration Management tool, which uses a child template data set, shown in Example 12-3 on page 252. When the business object editor is initialized, the Explorer view tree is populated using the object in the template data set, which, in this case, is the organizational object ConfigurationTop, which we defined in Example 12-2.
251
Note: If a wcfGetChildrenService element is defined as a child of the top object definition, then the Explorer tree root nodes are created based on the objects that this service returns. For the Custom Application Configuration Management tool, we use a single organizational object, ConfigurationTop, as the root node.
Example 12-3 Top object definition
<!-- Top object definition. Template datadet defined to use ConfigurationTop organiational object. --> <class name="extConfigTopObjectDefinition" extends="wcfTopObjectDefinition"> <dataset name="template"> <object objectType="ConfigurationTop"/> </dataset> </class> Based on this definition, the business object editor should display the organizational object, ConfigurationTop, with the display name, Configuration Categories (the display name value is taken from the resource bundle), and when expanded, list the available ConfigCategory objects as child nodes. See Figure 12-4 for a sample of the Custom Application Configuration Management tool Explorer view.
252
The primary object that owns this reference object is the parent, while the object type that is referenced is the child in this relationship. The following attributes are defined for this class: idProperty: Name of the object property that contains the unique ID for an instance of this object type. objectType: String that identifies the object type. referencedType: String that identifies the child object type in this relationship. In this case, the child object type is ConfigProperty. Example 12-4 shows the reference object definition. Example 12-4 Reference object definition <class name="extConfigCategoryPropertyReference" extends="wcfParentReferenceObjectDefinition" idProperty="childConfigPropertyId" objectType="ChildConfigProperty" referencedType="ConfigProperty"> </class>
253
name for an object is the value of its configCategoryName property, which is the value that is displayed in the Explorers tree view. Note: Example 12-5 does not have the complete class definition; instead, it displays parts of the class definition for reference. For the complete source code, refer to the cfgConfigCategoryPrimaryObjectDefinition.lzx file.
Example 12-5 Definition for ConfigCategory primary object
<class name="extConfigCategoryPrimaryObjectDefinition" extends="wcfPrimaryObjectDefinition" objectType="ConfigCategory" creatable="false" displayName="${extConfigResource.config_DisplayName.string}" displayNameProperty="configCategoryName" idProperty="configCategoryName" headerIcon="configurationHeaderIcon" icon="configurationIcon" newDisplayName="${extConfigResource.config_NewDisplayName.string}" > . . </class> The following child elements are declared within the primary object class definition: wcfGetChildrenService is declared as a child of this primary object definition, as shown in Example 12-6. This service returns a list of ConfigProperty child objects. The parameters that are passed to the request are: storeId: Store ID from context. configCategoryName: Value of the configCategoryName property of the ConfigCategory object. This service returns all of the configuration properties with the given category name.
Example 12-6 Get children service definition for ConfigCategory object
<wcfGetChildrenService url="/cmc/GetConfigCategoryChildren"> <wcfServiceParam name="storeId"/> <wcfServiceParam name="configCategoryName" propertyName="configCategoryName" /> </wcfGetChildrenService> <extConfigCategoryPropertyReference />
254
To define a parent-child relationship between the ConfigCategory and ConfigProperty object types, we instantiate the reference object extConfigCategoryPropertyReference, as shown in Example 12-6 on page 254. We discussed this reference objectReference object definition on page 252. When the Management Center user selects a ConfigCategory object in the explorer view, we want to display the ConfigProperty child objects that the wcfGetChildrenService returns as a list view in the main work area, as shown in Figure 12-5, which occurs using a wcfNavigationListDefinition declaration, as shown in Example 12-7. The following attributes are set for this navigation list definition: listClass: Name of the class that displays the child objects. In this case, we use extConfigPropertyList (See section Navigation list definition on page 261). isDefault: Set to true to indicate that this is the default navigation list. The default navigation list is displayed in the main work area when a ConfigCategory object is selected in the Explorer view. listTitle: Title for the navigation list, which is displayed above the list in the main work area. displayName: Display name for the navigation list, which is displayed in the context menu for the ConfigCategory object in the Explorer view.
Example 12-7 Navigation list definition
255
256
displayNameProperty="key" idProperty="configPropertyId" newDisplayName="${extConfigResource.config_PropertyNewDisplayName.strin g} " searchType="FindConfigProperties" propertiesClass="extConfigurationProperties" headerIcon="configPropertyHeaderIcon" icon="configPropertyIcon"> The following child elements are declared within this primary object definition class: An instance of extConfigPropertyValidator, which is an object-level validator that validates a ConfigProperty object. The class definition for this validator is in the cfgConfigPropertyObjectValidator.lzx file. It validates the value property of this primary object based on the following rules: If the type property is Integer, the value property should be an integer. If the type property is Float, the value property should be a float. If the type property is Date, the value property should be parsable, such as a date object. If the type property is DateTime, the value property should be parsable, such as a date object. If the type property is SimpleText, no validation occurs. The following service definitions, shown in Example 12-9 are declared for Create, Update, and Delete actions on the ConfigProperty object: wcfCreateService creates an object on the WebSphere Commerce server by invoking the URL that is specified in the url attribute. wcfUpdateService updates an object on the WebSphere Commerce server by invoking the URL that is specified in the url attribute. wcfDeleteService deletes an object on the WebSphere Commerce server by invoking the URL that is specified in the url attribute.
Example 12-9 Service definitions
<!-- Create service --> <wcfCreateService url="/cmc/CreateCustomAppProperties"> <wcfServiceParam name="storeId"/> </wcfCreateService> <!-- Update service --> <wcfUpdateService url="/cmc/UpdateCustomAppProperties">
257
<wcfServiceParam name="storeId"/> <wcfServiceParam name="configId" propertyName="configPropertyId" /> </wcfUpdateService> <!-- Delete service --> <wcfDeleteService url="/cmc/DeleteCustomAppProperties"> <wcfServiceParam name="storeId"/> <wcfServiceParam name="configId" propertyName="configPropertyId" /> </wcfDeleteService> Property definitions (wcfPropertyDefinition instances) for each property of the ConfigProperty primary object. Property definitions are optional, and you can use them to define information that built-in validators use and also to add custom validators for a property. For key, value, category, and description properties of the ConfigProperty object, we use property definitions, shown in Example 12-10, to set the following information: displayName: Display name for this property. propertyName: Name of the property being defined. required: Indicates if this is a required property. When set to true, the Management Center UI appends an asterisk to the display name. Setting this to true also enables a built-in validator, which ensures that the value of this property is not null or empty. type: Data type for this property. A built-in validator ensures that the value of this property can be converted to the specified type. maximumSize: Maximum size for this property. The built-in property size validator uses this value.
Example 12-10 Property definitions for key, value, category and description
<!-- Property definition for ConfigProperty.key --> <wcfPropertyDefinition displayName="${extConfigResource.keyPropertyName.string}" propertyName="key" required="true"type="string" maximumSize="100"/> <!-- Property definition for ConfigProperty.value --> <wcfPropertyDefinition displayName="${extConfigResource.valuePropertyName.string}" propertyName="value" required="true" type="string" maximumSize="254"/> <!-- Property definition for ConfigProperty.category --> <wcfPropertyDefinition
258
displayName="${extConfigResource.categoryPropertyName.string}" propertyName="category" required="true" type="string" maximumSize="100"/> <!-- Property definition for ConfigProperty.description --> <wcfPropertyDefinition displayName="${extConfigResource.descPropertyName.string}" propertyName="description" type="string" maximumSize="254"/> For the type property of ConfigProperty object, in addition to the attributes that we previously mentioned, we also create wcfPropertyValue instances to provide a list of selectable values for this property, as shown in Example 12-11. Attributes set for each wcfPropertyValue are: displayName: Displays the name for the property value. value: The value that is stored in the object. The possible values defined are SimpleText, Integer, Float, Date, and DateTime.
Example 12-11 Property definitions for type
<!-- Property definition for ConfigProperty.type --> <wcfPropertyDefinition propertyName="type" displayName="${extConfigResource.typePropertyName.string}" maximumSize="50" required="true" type="string"> <wcfPropertyValue displayName="${extConfigResource.typeString.string}" value="SimpleText"/> <wcfPropertyValue displayName="${extConfigResource.typeInteger.string}" value="Integer"/> <wcfPropertyValue displayName="${extConfigResource.typeDecimal.string}" value="Float"/> <wcfPropertyValue displayName="${extConfigResource.typeDate.string}" value="Date"/> <wcfPropertyValue displayName="${extConfigResource.typeTimestamp.string}" value="DateTime"/> </wcfPropertyDefinition>
259
definition class, extFindConfigurations, is defined in the cfgFindConfigSearchDefinition.lzx file, under the following directory: LOBTools/WebContent/WEB-INF/src/lzx/redbooks/config/searchDefinitions The following attributes are set for the search definition: searchType: A string that identifies the search type. displayName: Display name to be used for this search type. isDefault: Set to true, which indicates that this is the default search type. listClass: Name of the grid class to be used to display search results. In this case, extConfigPropertySearchGrid (See Search list definition on page 264). listTitle: Title to be displayed in the search results view. An instance of the search service (wcfSearchService), which was created as a child of this search definition, specifies the search URL in its url attribute.
Example 12-12 Search definition
<class name="extFindConfigurations" extends="wcfSearchDefinition" searchType="FindConfigProperties" displayName="${extConfigResource.config_PropertyDisplayName.string}" isDefault="true" listClass="extConfigPropertySearchGrid" listTitle="${extConfigResource.propertySearchTitle.string}"> <wcfSearchService name="findConfigProperties" url="/cmc/FindConfigProperties"> <wcfServiceParam name="storeId" /> </wcfSearchService> </class> You can initiate this default search either from the Search bar in the right corner of the Management Center menu area, as shown in Figure 12-6 or from the Search bar in the Utilities view, shown in Figure 12-7 on page 261.
260
261
<attribute name="objectTypes" value="ChildConfigProperty"/> </class> Object grid (wcfObjectGrid) class extConfigPropertyGrid. This is the grid class used by navigation child list editor extConfigPropertyList to display ConfigProperty objects in the list view. The object grid class definition sets its preferenceKey attribute, as shown in Example 12-14. When columns in this grid are reconfigured by a Management Center user, the new column settings are saved in the Preference Manager, only if the preferenceKey attribute value is set. wcfGridText is used to display property values in each column of the grid, as shown in Example 12-14. The following grid column attributes are set for each wcfGridText element: editable: If set to true, the column allows in place editing. propertyName: Name of the object property to display in this column. objectPath: Used to specify the path to locate the property set in the propertyName attribute. The object type that this grid uses is the ChildConfigProperty reference object, as shown in Example 12-4 on page 253. Therefore, to resolve the value for the propertyName attribute from the child object, we use the ConfigProperty as the object path. datatype: The columns data type. text: Text to display in the column header. visible: If set to true, the column is visible. You can modify this setting by reconfiguring the grid. required: If set to true, it indicates that this is a required column. A Management Center user cannot hide required columns.
Example 12-14 Navigation list grid definition
<class extends="wcfObjectGrid" name="extConfigPropertyGrid" preferenceKey="extConfigPropertyBrowseGrid"> <wcfGridText editable="false" name="configPropertyId" objectPath="ConfigProperty" propertyName="configPropertyId" text="${extConfigResource.configPropertyIdColumnHeader.string}" visible="false" width="90"/> <wcfGridText editable="false" name="category" objectPath="ConfigProperty" propertyName="category" text="${extConfigResource.configCategoryColumnHeader.string}" visible="false" width="90"/> <wcfGridText editable="false" name="key" objectPath="ConfigProperty" propertyName="key" required="true"
262
datatype="string" text="${extConfigResource.keyColumnHeader.string}" visible="true" width="200"/> <wcfGridText editable="false" name="value" objectPath="ConfigProperty" propertyName="value" datatype="string" text="${extConfigResource.valueColumnHeader.string}" visible="true" width="220"/> <wcfGridText editable="false" name="type" objectPath="ConfigProperty" propertyName="type" datatype="string" text="${extConfigResource.typeColumnHeader.string}" visible="true" width="100"/> <wcfGridText editable="false" name="description" objectPath="ConfigProperty" propertyName="description" datatype="string" text="${extConfigResource.descColumnHeader.string}" visible="false" width="300"/> </class> Figure 12-8 shows the default navigation grid display. The Management Center can change the columns that are displayed and their order.
263
<class extends="wcfObjectGrid" name="extConfigPropertySearchGrid" preferenceKey="extConfigPropertySearchGrid"> <wcfGridText editable="false" name="configPropertyId" propertyName="configPropertyId" text="${extConfigResource.configPropertyIdColumnHeader.string}" visible="false" width="90"/> <wcfGridText editable="false" name="category" propertyName="category" text="${extConfigResource.configCategoryColumnHeader.string}" visible="true" width="220"/> <wcfGridText editable="false" name="key" propertyName="key" required="true" datatype="string" text="${extConfigResource.keyColumnHeader.string}" visible="true" width="200"/> <wcfGridText editable="false" name="value" propertyName="value" datatype="string" text="${extConfigResource.valueColumnHeader.string}" visible="true" width="220"/> <wcfGridText editable="false" name="type" propertyName="type" datatype="string" text="${extConfigResource.typeColumnHeader.string}" visible="true" width="100"/> <wcfGridText editable="false" name="description" propertyName="description" datatype="string" text="${extConfigResource.descColumnHeader.string}" visible="false" width="300"/> </class>
264
Figure 12-9 shows the default search result grid display. The Management Center can change the columns that are displayed and their order.
265
The property view definition class, extConfigurationProperties (Example 12-17), is defined in the cfgConfigurationPropertiesView.lzx file, which is located under the following directory: LOBTools/WebContent/WEB-INF/src/lzx/redbooks/config/propertiesViews We need only one content pane to display all of the property editors, so we use a wcfPropertyPane. Inside the pane, property editors are grouped using the wcfPropertyGroup class, as shown in Example 12-17. All of the property editor classes set the following attributes: propertyName: Name of the ConfigProperty objects property that this editor should bind to. required: If set to true, a value is required for this property. promptText: The label to be displayed for this property editor. Note: Example 12-17 does not have the complete class definition. It displays parts of the class definition for reference. For the complete source code refer to the cfgConfigurationPropertiesView.lzx file.
Example 12-17 Property view class definition
266
<!-- Grouping for category, type key and description property editors --> <wcfPropertyGroup name="group" collapsable="false"> <extConfigCategoryPropertyInputText propertyName="category" required="true" promptText="${extConfigResource.categoryPropertyName.string}"/> <wcfPropertyCombobox propertyName="type" required="true" promptText="${extConfigResource.typePropertyName.string}" /> <wcfPropertyInputText propertyName="key" required="true" promptText="${extConfigResource.keyPropertyName.string}" /> <wcfPropertyInputMultiLineText propertyName="description" required="false" promptText="${extConfigResource.descPropertyName.string}" /> </wcfPropertyGroup> . . . </wcfPropertyPane> </class> Editors for the category, type, key, and description properties are defined in one property group. Editors for the value property are defined in separate groups, which we discuss in Object property: value on page 270. In the following sections, we go through each of the properties and the editors that we provide for them.
267
A custom editor class, extConfigCategoryPropertyInputText, is used to bind to the category property, as shown in Example 12-18.
Example 12-18 Custom editor for category property
<extConfigCategoryPropertyInputText propertyName="category" required="true" promptText="${extConfigResource.categoryPropertyName.string}"/> This class extends from the wcfPropertyInputText class and is defined in the cfgConfigurationPropertyEditors.lzx file, which is located in the following directory: LOBTools/WebContent/WEB-INF/src/lzx/redbooks/config/propertiesViews In this custom editor class, we override the setModelObject method to check if the category name can be resolved from the ConfigProperty objects parent primary object. If available, we set the category property value, as shown in Example 12-19 on page 269.
268
Note: In our scenario, ConfigCategory is modeled as a client-side parent object to the ConfigProperty object for presenting an Explorer view of all distinct configuration categories. There is no corresponding business object on the WebSphere Commerce server. If ConfigCategory was a real parent object on the server side, then we would model it as a creatable object and use a wcfReferenceEditor here instead of this custom editor.
Example 12-19 Custom editor for category property
<class name="extConfigCategoryPropertyInputText" extends="wcfPropertyInputText"> <method name="getConfigCategoryName"> <![CDATA[ var category = null; if (typeof(this.o) != "undefined" && this.o != null) { var topObj = this.o.getParentPrimaryObject("ConfigCategory"); if (topObj != null) { category = topObj.getPropertyValue("configCategoryName",""); } } return category; ]]> </method> <method name="setModelObject" args="newObject"> <![CDATA[ super.setModelObject(newObject); var category = this.o.getPropertyValue("category", ""); if (category == null || category == "") { category = getConfigCategoryName(); if (category != null) this.o.setPropertyValue("category", "", category); } ]]> </method> </class>
269
270
Table 12-1 value property editors Property type SimpleText Integer Float Date DateTime Any other type Editor wcfPropertyInputText wcfPropertyNumericText wcfPropertyNumericText wcfPropertyDatePicker wcfPropertyDateTimePicker wcfPropertyInputText
To selectively display editors based on the property type that is selected, we use the wcfEnablementCondition class, which is used to declare an enablement condition to indicate whether the parent object should be enabled. You can reach instances of this class as children of the wcfPropertyGroup class. We define multiple property groups for the value property, one for each editor type that we support. Each property group declares a wcfEnablementCondition child. We use the following attributes of the wcfEnablementCondition class to selectively enable these property groups: propertyName: Name of the objects property to be evaluated. In this case, this is the type property. enablementValue: This is the value that the type property must be set to for the property group to be enabled. The property group groupText, shown in Example 12-23 declares a wcfEnablementCondition with the enablementValue attribute of SimpleText. This property group is displayed only if the type property value is SimpleText.
Example 12-23 Property group for SimpleText property type
<wcfPropertyGroup name="groupText" collapsable="false"> <wcfEnablementCondition propertyName="type" enablementValue="SimpleText" /> <wcfPropertyInputText propertyName="value" required="true" promptText="${extConfigResource.valuePropertyName.string}" /> </wcfPropertyGroup> We use the same approach to define property groups for Integer, Float, Date, and DateTime property types, as shown in Example 12-24 on page 272.
271
Example 12-24 Property groups for Integer, Float, Date and DateTime property types
<!-- Integer value editor group --> <wcfPropertyGroup name="groupInteger" collapsable="false"> <wcfEnablementCondition propertyName="type" enablementValue="Integer" /> <wcfPropertyNumericText propertyName="value" required="true" numDecimalPlaces="0" showDecimals="false" promptText="${extConfigResource.valuePropertyName.string}" /> </wcfPropertyGroup> <!-- Decimal value editor group --> <wcfPropertyGroup name="groupDecimal" collapsable="false"> <wcfEnablementCondition propertyName="type" enablementValue="Float" /> <wcfPropertyNumericText propertyName="value" required="true" numDecimalPlaces="2" showDecimals="true" promptText="${extConfigResource.valuePropertyName.string}" /> </wcfPropertyGroup> <!-- Date value editor group --> <wcfPropertyGroup name="groupDate" collapsable="false"> <wcfEnablementCondition propertyName="type" enablementValue="Date" /> <wcfPropertyDatePicker propertyName="value" required="true" promptText="${extConfigResource.valuePropertyName.string}" /> </wcfPropertyGroup> <!-- DateTime value editor group --> <wcfPropertyGroup name="groupDateTime" collapsable="false"> <wcfEnablementCondition propertyName="type" enablementValue="DateTime" /> <wcfPropertyDateTimePicker propertyName="value" required="true" promptText="${extConfigResource.valuePropertyName.string}" /> </wcfPropertyGroup>
272
To declare a default editor, we add one more property group, as shown in Example 12-25. This property group uses a wcfEnablementAndCondition declaration. This class enables a parent object if all of the child wcfEnablementCondition are true.
Example 12-25 Default property group
<!-Default value editor group. --> <wcfPropertyGroup name="groupDefault" collapsable="false"> <wcfEnablementAndCondition> <wcfEnablementCondition propertyName="type" enablementValue="SimpleText" negate="true" /> <wcfEnablementCondition propertyName="type" enablementValue="Date" negate="true" /> <wcfEnablementCondition propertyName="type" enablementValue="DateTime" negate="true" /> <wcfEnablementCondition propertyName="type" enablementValue="Integer" negate="true" /> <wcfEnablementCondition propertyName="type" enablementValue="Float" negate="true" /> </wcfEnablementAndCondition> <wcfPropertyInputText propertyName="value" required="true" promptText="${extConfigResource.valuePropertyName.string}" /> </wcfPropertyGroup> Figure 12-12 on page 274 shows the value editor displayed when the SimpleText property editor is selected.
273
Figure 12-13 shows the value editor displayed when the property type is changed to Date.
274
<library> <class name="extConfigResourceBundle" extends="wcfResourceBundle" baseName="com.redbooks.commerce.client.lobtools.properties.ConfigLOB"> <wcfResourceBundleKey name="configTopDisplayName"/> <wcfResourceBundleKey name="config_DisplayName"/> <wcfResourceBundleKey name="config_NewDisplayName"/> <wcfResourceBundleKey name="config_PropertyDisplayName"/> <wcfResourceBundleKey name="config_PropertyNewDisplayName"/> <wcfResourceBundleKey name="propertyList"/> <wcfResourceBundleKey name="propertyListDisplayName"/> <wcfResourceBundleKey name="propertySearchTitle"/> <wcfResourceBundleKey name="configCategoryFilter"/> . . . </class> <extConfigResourceBundle id="extConfigResource"/> </library> The properties file, ConfigLOB.properties, is created under the package com.redbooks.commerce.client.lobtools.properties, which is located in the LOBTools/src folder.
275
<library> <resource name="configTreeFolderIcon" src="resources/configroot.png" /> <resource name="configurationHeaderIcon" src="resources/descriptive_attr.png" /> <resource name="configurationIcon" src="resources/descriptive_attr_small.png" /> <resource name="configPropertyHeaderIcon" src="resources/defining_attr.png" /> <resource name="configPropertyIcon" src="resources/defining_attr_small.png" /> </library>
276
<include href="objectDefinitions/cfgConfigObjectDefinitions.lzx"/> <include href="listViewDefinitions/cfgConfigurationPropertiesGrid.lzx" /> <include href="listViewDefinitions/cfgConfigurationPropertiesSearchGrid.lzx" /> <include href="objectDefinitions/cfgConfigPropertyPrimaryObjectDefinition.lzx"/> <include href="objectDefinitions/cfgConfigCategoryPrimaryObjectDefinition.lzx"/> <include href="objectDefinitions/cfgConfigTopObjectDefinition.lzx"/> <include href="propertiesViews/cfgConfigurationPropertyEditors.lzx" /> <include href="propertiesViews/cfgConfigurationPropertiesView.lzx" /> <include href="cfgConfigManagementFilterDefinitions.lzx" /> <include href="searchDefinitions/cfgFindConfigSearchDefinition.lzx" /> <include href="cfgConfigManagementToolDefintion.lzx"/> </library>
12.3.10 Adding the Custom Application Configuration Management tool to Management Center
In the following sections, we explain customizations that you must make to add the new tool to the Management Center.
Defining the label and icon for the new menu item
To define the new menu item label and icon, we create the following OpenLaszlo files under the LOBTools/WebContent/WEB-INF/src/lzx/redbooks/shell directory: extResources.lzx: This file defines all image resources that are used for menu icons, as shown in Example 12-29. We included image files for the new menu item icon in the zip archive for this scenario.
Example 12-29 Shell extension resources
<library> <resource name="configurationActiveTabIcon" src="resources/descriptive_attr_small.png" /> <resource name="configurationInactiveTabIcon" src="resources/descriptive_attr_small.png" /> </library> extShellResourceBundle.lzx: Resource bundle definition for menu item labels (Example 12-30 on page 278).
277
<library> <class name="extShellResourceBundle" extends="wcfResourceBundle" baseName="com.redbooks.commerce.client.lobtools.properties.extShellLOB" > <wcfResourceBundleKey name="configManagementDisplayName" /> </class> <extShellResourceBundle id="extShellResources"/> </library> The resource bundle properties file, extShellLOB.properties, is created in the package com.redbooks.commerce.client.lobtools.properties, which is located in the LOBTools/src folder.
278
<library> <!-- Include shell resource bundle extension --> <include href="../../redbooks/shell/extShellResourceBundle.lzx" /> <!-- Include resources --> <include href="../../redbooks/shell/extResources.lzx" /> <!--- Include library for Configuration Management tool. --> <include href="../../redbooks/config/cfgConfigLibrary.lzx" /> </library>
We update the ApplicationMenuItems.lzx file, which is located in the following directory: LOBTools/WebContent/WEB-INF/src/lzx/commerce/shell We add a new menu item for our new tool, as shown in Example 12-32 on page 280. The objectClass attribute specifies the business object editor name extConfigManagement. The usage attribute specifies the role that is authorized to use the new tool. For this scenario, we used the existing IBM_CatalogTool role.
279
Note: Information about changing access control for a Management Center tool is at the following Web site: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfcustomizeshel l.htm
Example 12-32 Menu item definition for CACMT
<wcfApplicationMenuItem id="configManagement" width="${parent.width}" height="25" activeIconSrc="configurationActiveTabIcon" inactiveIconSrc="configurationInactiveTabIcon" displayName="${extShellResources.configManagementDisplayName.string}" objectClass="extConfigManagement" usage="IBM_CatalogTool" actionName="openBusinessObjectEditor" />
280
Note: To separate the logical representation of a configuration setting from a configuration setting category, two different nouns is the more sophisticated approach. However, Feature Pack 3.0.1 does not support multiple nouns that are mapped to a single table; therefore, we use only one noun that represents both the settings and the category. Of course you can create a second table that holds the configuration setting categories, which enables you to introduce a separate noun for a configuration setting category. Figure 12-15 on page 282 represents the assets that you create during this scenario. As you progress through the scenario, you will see different versions of the diagram, with the relevant asset for the current step highlighted.
281
appdef
DPTK
Access control policy XML Command registration Service module configuration Business object mediator configuration Value mapping
CustomAppProperties-Server acpload massload Business logic Get commands Change commands Business Object Mediators Read mediators Physical object persistence service Physical SDO Java classes Data Service Layer Wizard Change mediators Process commands Sync commands
Object-relational metadata
Query templates
Database
Noun XSD/WSDL
Ecore, genmodel
Figure 12-15 Overview of assets to be created for the Custom Application Configuration Management tool
282
Installed WebSphere Commerce Feature Pack 3.0.1. http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.admin.doc/tasks/tigfepmaster.htm Enabled WebSphere Commerce services for Feature Pack 3.0.1 http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tasks/twvenableservices.htm Set up your development environment. http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tasks/twvsetupdevenv.htm Installing the Design Pattern Toolkit and the ComponentProjects design pattern http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tasks/twvdptk.htm
283
appdef
DPTK
To generate CustomAppProperties service module projects: 1. Start WebSphere Commerce Developer. 2. Open the J2EE perspective. 3. Create the application definition file to create the base code for the CustomAppProperties service module: a. Expand the Other Projects ComponentProject newServiceModule folder. b. Right-click the components folder, and select New Other Simple File. Click Next. c. In the File name field, type customAppProperties.appdef, and click Finish. d. Double-click the customAppProperties.appdef file to open it. e. Paste the application definition from Example 12-33 into the file, and then save the file. The definition indicates the name of the noun (CustomAppProperties) and the verbs that are enabled for the pattern (Get, Process, and Change but not Sync). This file determines the assets and code to be generated for you. The meaning of the other attributes are described in Creating a WebSphere Commerce service module of the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com. ibm.commerce.component-services.doc/tasks/twvcreatecomponent.htm
Example 12-33 CustomAppProperties definition file customAppProperties.appdef
284
name="CustomAppProperties" get="true" process="true" change="true" sync="false" /> </commerce-component> 4. Right-click CustomAppProperties.appdef, and select Apply Pattern. 5. Select WebsphereCommerce Component Projects, as shown in Figure 12-17.
6. Click OK. The pattern is applied. 7. Click OK to close the message box, and switch to the Navigator view to verify that the following projects are created: CustomAppProperties-Server CustomAppProperties-Client CustomAppProperties-UnitTests CustomAppProperties-DataObjects CustomAppPropertiesServicesJMSInterface CustomAppPropertiesServicesHTTPInterface
285
8. The DTPK pattern generates some code in the CustomAppProperties-Server project that you must move to the WebSphere Commerce installation. Using Windows Explorer, copy the folder <workspace_dir>/CustomAppProperties-Server/TODO-MoveItToWCServerX MLFolder/xml from the CustomAppProperties-Server project into the <WCDE_installdir>/xml directory. Note: Build errors will show up in the projects at this stage because the CustomAppProperties data objects are not yet generated from the CustomAppProperties noun. Continue the scenario to generate these objects and to resolve the build errors.
Note: Also, validation errors on XML and XSD files are shown, especially in the folder <workspace_dir>/CustomAppProperties-Server/TODO-MoveItToWCServerX MLFolder/xml. You might find it comfortable to either move the folder out of the workspace or to deactivate XML Schema Validation in WebSphere Commerce Developer for the concerning projects. You created the service module projects, and you are now ready to implement specific logic to your service.
286
The noun that is used in this scenario is called the CustomAppProperties noun. This noun represents the logical data model of the new component that you are going to create. Remember, that the logical data model that the logical SDOs represent is shared across all clients who use your service. The generated code from the CustomAppProperties noun is stored in the CustomAppProperties-DataObjects project, which we outline in Figure 12-18 on page 286. Before you define the noun, you must prepare the CustomAppProperties-DataObjects project: 1. The foundation data objects are provided in the WebServicesRouter project in the following directory: <workspace_dir>\WebServicesRouter\WebContent\component-services
287
288
In order for the WebSphere Commerce server to differentiate between different levels of client libraries, which might have different versions of the WebSphere Commerce nouns, the versionID attribute of the OAGIS BusinessObjectDocumentType is populated with the WebSphere Commerce version that is associated with the noun. You always need to include this element. For more information, refer to the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/concepts/csdbodversion.htm
Figure 12-21 on page 290 displays the structure of the pre-built noun in detail. The CustomAppProperties noun consists of the following elements: /CustomAppProperties/CustomAppPropertiesIdentifier This element encapsulates all identifier elements that you want to provide. By default, the following identifiers are provided: /CustomAppProperties/CustomAppPropertiesIdentifier/UniqueID
289
The UniqueID is mapped later on the primary key of the XAPPPROPS database table. /CustomAppProperties/CustomAppPropertiesIdentifier/ExternalIdentifier/Name You extend the ExternalIdentifier later by adding another field to reflect the unique index of the XAPPPROPS database table. Description A pre-built field substitutional for all data fields you will later provide. UserData A pre-built field that acts as an extension point for future extensions of the CustomAppProperties noun with additional data. This field is set of type UserData that BaseTypes.xsd imports. We strongly recommend that you keep this field in every noun. For more information about extending a noun, refer to the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tasks/twvextendnoun.htm If you look for practical application for extending a noun, refer to the WebSphere Commerce Information Center, tutorial Adding new properties to a WebSphere Commerce service using the Data Service layer at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tutorial/twvfounduserdata.htm
290
After you apply all changes, your CustomAppProperties noun will have a structure similar to Figure 12-22 on page 291.
To update the default noun to the target structure: 1. Using the graphical editor of WebSphere Commerce Developer or by directly editing the XSD source code, add the following elements: a. /CustomAppProperties/CustomAppPropertiesIdentifier/ExternalIdentifier/St oreent_id Together with Name, this element represents a unique index and an alternative identifier to UniqueID. It is populated with the unique identifier of the store to which this configuration setting belongs. i. Add this type as int (Built-in simple type). ii. Update the minOccurs and the maxOccurs attributes to 1. Note: We decided to include a flat representation of foreign relationship to a WebSphere Commerce store. Alternatively, you can import CustomAppProperties-DataObjects\xsd\OAGIS\9.0\Overlays\IBM\Com merce\Resources\Components\IdentifierTypes.xsd and use the StoreIdentifierType type. b. /CustomAppProperties/Value
291
This field represents the value of the configuration setting. The data stored in this field might need additional interpretation depending on the value that is stored in the ValueType field. Add this value as type String (Built-in simple type). c. /CustomAppProperties/ValueType This field represents the value type of the configuration setting. It provides the client with information about how to interpret the data that is stored in the field Value. To enforce proper operation, this field cannot be empty or null and must have one of the following fixed values: SimpleText Code Date Time DateTime Color Integer Float Email URL
Add this field as a Simple Type: i. By using the Properties view of the XSD editor, name the newly created type ValueTypeEnum, then select atomic as the Variety, and select normalized String as the Base Type. ii. From the Enumeration tab of the Properties view, add the list of fixed values to the ValueTypeEnum type, as shown in Figure 12-24 on page 294 and Figure 12-23 on page 293.
292
293
iii. Select the ValueType field, and change the type to redbooks:ValueTypeEnum (User defined simple type). iv. Update the minOccurs and maxOccurs attributes to 1. d. /CustomAppProperties/Category This field represents the category of the configuration setting: i. Add this field as type String (Built-in simple type). ii. Update minOccurs attribute to 1, and maxOccurs attribute to 1. 2. Use the graphical editor of WebSphere Commerce Developer or directly edit the XSD source code, and update the following elements: /CustomAppProperties/CustomAppPropertiesIdentifier/UniqueID This field represents the primary key of the configuration setting. Change this field to type long (Built-in simple type). The following elements remain in the noun definition and serve for the following purposes: /CustomAppProperties/CustomAppPropertiesIdentifier This field represents the nouns identifier. /CustomAppProperties/CustomAppPropertiesIdentifier/ExternalIdentifier This field represents the nouns external identifier. /CustomAppProperties/CustomAppPropertiesIdentifier/ExternalIdentifier/Name Together with Storeent_id, this field represents a unique index and an alternative identifier to UniqueID.
294
/CustomAppProperties/Description This field holds an administrative description as a reminder for the line-of-business user. /CustomAppProperties/UserData This field enables the extension of the CustomAppProperties noun by using userData extension. You should end up with an XSD, as shown in Example 12-34.
Example 12-34 CustomAppProperties.xsd
<?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmlns:oa="http://www.openapplications.org/oagis/9" xmlns:_wcf="http://www.ibm.com/xmlns/prod/commerce/9/foundation" xmlns:redbooks="http://www.redbooks.ibm.com/xmlns/prod/commerce/9" attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://www.redbooks.ibm.com/xmlns/prod/commerce/9" ecore:nsPrefix="redbooks" ecore:package="com.redbooks.commerce.customappproperties.facade.datatypes"> <annotation> <documentation> This file contains all type definitions that make up a CustomAppProperties. </documentation> </annotation> <import namespace="http://www.ibm.com/xmlns/prod/commerce/9/foundation" schemaLocation="../../../../IBM/Commerce/Resources/Components/BaseTypes.xsd" /> <complexType name="CustomAppPropertiesExternalIdentifierType"> <annotation> <documentation> The type definition of an external identifier for a CustomAppProperties. </documentation> </annotation> <sequence> <element maxOccurs="1" minOccurs="1" name="Name" type="string"> <annotation> <documentation> The name of the CustomAppProperties. </documentation> </annotation> </element>
295
<element name="Storeent_id" type="int" minOccurs="1" maxOccurs="1"> <annotation> <documentation> The reference to a store of the CustomAppProperties </documentation> </annotation> </element> </sequence> <attribute name="ownerID" type="string" use="optional"> <annotation> <documentation>The owner of a CustomAppProperties.</documentation> </annotation> </attribute> </complexType> <complexType name="CustomAppPropertiesIdentifierType"> <annotation> <documentation> The type definition of an CustomAppProperties identifier. </documentation> </annotation> <sequence> <element maxOccurs="1" minOccurs="1" name="UniqueID" type="long"> <annotation> <documentation>The CustomAppProperties ID.</documentation> </annotation> </element> <element maxOccurs="1" minOccurs="0" name="ExternalIdentifier" type="redbooks:CustomAppPropertiesExternalIdentifierType"> <annotation> <documentation> The external identifier of the CustomAppProperties. </documentation> </annotation> </element> </sequence> </complexType> <element name="CustomAppPropertiesBODVersion" type="string" fixed="6.0.0.4"> <annotation> <documentation> The version of WebSphere Commerce when the CustomAppProperties noun was last changed. The value is set as the versionID attribute in the corresponding BODs. </documentation> </annotation> </element>
296
<complexType name="CustomAppPropertiesType"> <annotation> <documentation>Type definition of a CustomAppProperties.</documentation> </annotation> <sequence> <element maxOccurs="1" minOccurs="1" name="CustomAppPropertiesIdentifier" type="redbooks:CustomAppPropertiesIdentifierType"> <annotation> <documentation> The CustomAppProperties identifier. </documentation> </annotation> </element> <element maxOccurs="1" minOccurs="0" name="Description" type="string"> <annotation> <documentation> Description of the CustomAppProperties </documentation> </annotation> </element> <element maxOccurs="1" minOccurs="0" ref="_wcf:UserData"> <annotation> <documentation>The user data area.</documentation> </annotation> </element> <element name="Value" type="string" minOccurs="0" maxOccurs="1"> <annotation> <documentation>Value of the CustomAppProperties</documentation> </annotation> </element> <element name="Category" type="string" minOccurs="1" maxOccurs="1"> <annotation> <documentation> Category of the CustomAppProperties </documentation> </annotation> </element> <element name="ValueType" type="redbooks:ValueTypeEnum" minOccurs="1" maxOccurs="1"> <annotation> <documentation> ValueType of the CustomAppProperties </documentation> </annotation> </element>
297
</sequence> </complexType> <element name="CustomAppProperties" type="redbooks:CustomAppPropertiesType"> <annotation> <documentation> Element definition of the CustomAppProperties. </documentation> </annotation> </element> <simpleType name="ValueTypeEnum"> <annotation> <documentation> Represents the value type of the configuration setting. It provides the client with information, how to interpret the data stored in the field Value. </documentation> </annotation> <restriction base="normalizedString"> <enumeration value="SimpleText"></enumeration> <enumeration value="Code"></enumeration> <enumeration value="Date"></enumeration> <enumeration value="Time"></enumeration> <enumeration value="DateTime"></enumeration> <enumeration value="Color"></enumeration> <enumeration value="Integer"></enumeration> <enumeration value="Float"></enumeration> <enumeration value="Email"></enumeration> <enumeration value="URL"></enumeration> </restriction> </simpleType> </schema> You created the logical data model of the CustomAppProperties noun. Your next step is to generate the logical SDOs.
298
Note: In this step, you generate code for the CustomAppProperties-DataObjects project. The code generator takes note of already existing code and will not overwrite resources. To make sure that the code gets completely regenerated, repeat this step. We recommend that you delete all packages in CustomAppProperties-DataObjects/src before. To generate the SDOs for the CustomAppProperties noun: 1. Open the CustomAppProperties-DataObjects\ecore folder. 2. Right-click CustomAppProperties.genmodel, and select Reload. 3. Select Load from XML Schema. Click Next. Be sure that no error message appears. The wizard checks the list of XSDs in the XML Schema URI field for validation errors, and when it finds an error, an error message window appears. If this happens, go back to your customAppProperties.xsd noun definition file and correct all errors. Also be sure that customAppProperties.xsd is in the list of the XML Schema URI field. You might find it comfortable to copy the field content into an external text editor for better readability. Figure 12-25 illustrates the XML Schema Import definition.
4. Click Next again, and then click Browse. 5. Expand the Workspace in the left pane, and navigate to the CustomAppProperties-DataObjects\ecore folder.
299
7. Click OK. 8. In the Referenced generator models area, locate the Oagis9 model area, and select Oagis9CodeLists, Oagis9, and Oagis9UnqualifiedDatatypes, as shown in Figure 12-27 on page 301. Under Foundation model, select the CommerceFoundation model.
300
9. Click Finish. 10.Open CustomAppProperties.genmodel. 11.Right-click the root element, and select Set SDO Defaults. 12.Save the model (Ctrl+S). 13.Under the CustomAppProperties root, right-click the CustomAppProperties element, and select Generate Model Code, which generates the CustomAppProperties-DataObjects code. 14.Verify that the CustomAppProperties-DataObjects project has no compilation errors.
301
Take a moment to examine the most important assets that the current activity generated. Go to CustomAppProperties-DataObjects/src, and expand the packages: com.redbooks.commerce.customappproperties.facade.datatypes This package mainly contains Java interfaces that represent the: Data types that are used in the CustomAppProperties noun. Data types that are used to form request and respond documents that cover the verbs Get, Change, and Process. Document root and a factory to generate CustomAppProperties noun, the request data types, and the response data types. com.redbooks.commerce.customappproperties.facade.datatypes.impl This package contains the implementation classes for the data types that are defined in the following package: com.redbooks.commerce.customappproperties.facade.datatypes com.redbooks.commerce.customappproperties.facade.datatypes.util This package contains several implementation classes that are used as factories of the noun, the request data types, and the response data types. We need to update the class that represents the user-defined simple type, ValueTypeEnum, because as of the current implementation in Feature Pack 3.0.1 there is an error in type conversion. This error will be solved in the next Fix Pack. Follow these steps to implement a workaround: 1. Open CustomAppProperties-DataObjects/src. 2. Under the com.redbooks.commerce.customappproperties.facade.datatypes.impl package, open the CustomAppPropertiesTypeImpl.java file in the Java editor. 3. Navigate to the method public void eSet(EStructuralFeature eFeature, Object newValue). 4. Replace the following code with the code in Example 12-35: case CustomAppPropertiesPackage.CUSTOM_APP_PROPERTIES_TYPE__VALUE_TYPE: setValueType((ValueTypeEnum) newValue); return;
Example 12-35 Workaround ti fix wrong code conversion for ValueTypeEnum
case CustomAppPropertiesPackage.CUSTOM_APP_PROPERTIES_TYPE__VALUE_TYPE: //Workaround for wrong type conversion if (newValue instanceof ValueTypeEnum) {
302
setValueType((ValueTypeEnum) newValue); } else { setValueType(ValueTypeEnum.get((String) newValue)); } return; Note: If you need to regenerate the logical SDOs, you might need to re-apply the changes that we described here.
12.4.5 Generating the Persistence layer for the CustomAppProperties service module
The BOD command framework uses the Data Service layer that is independent of the actual physical schema. WebSphere Commerce Developer provides a Data Service layer wizard to generate object-relational metadata and physical data objects that represent the database schema. Use this wizard to create all necessary assets, as shown in Figure 12-28.
CustomAppProperties-Server
Physical object persistence service Physical SDO Java classes Data Service Layer Wizard Database
Figure 12-28 Generating the Persistence layer for the CustomAppProperties service module
303
ALTER TABLE "XAPPPROPS" DROP FOREIGN KEY "F_10001"; DROP INDEX "X0000001"; DROP TABLE "XAPPPROPS"; CREATE TABLE "XAPPPROPS" ( "STOREENT_ID" INTEGER NOT NULL, "XAPPPROPS_ID" BIGINT NOT NULL, "OPTCOUNTER" SMALLINT, "NAME" CHARACTER(254) NOT NULL, "VALUE" VARCHAR(2000), "DESCRIPTION" VARCHAR(2000), "VALUETYPE" CHARACTER(254) NOT NULL, "CATEGORY" CHARACTER(254), "VERSION" INTEGER, "FIELD1" BIGINT, "FIELD2" FLOAT, "FIELD3" VARCHAR(2000), "FIELD4" VARCHAR(2000), "FIELD5" VARCHAR(2000) ); ALTER TABLE "XAPPPROPS" ADD CONSTRAINT "XAPPPROPS_PK" PRIMARY KEY ("XAPPPROPS_ID"); ALTER TABLE "XAPPPROPS" ADD CONSTRAINT "F_10001" FOREIGN KEY ("STOREENT_ID")
304
REFERENCES "STOREENT"("STOREENT_ID") ON DELETE CASCADE ON UPDATE NO ACTION; CREATE UNIQUE INDEX X0000001 ON XAPPPROPS ( STOREENT_ID ASC, NAME ASC ); INSERT INTO KEYS (KEYS_ID, TABLENAME, COLUMNNAME, COUNTER, PREFETCHSIZE, LOWERBOUND, UPPERBOUND) values (5001, 'xappprops', 'xappprops_id', 10000, 1, 10000, 9223372036850000000); Table 12-2, the XAPPPROPS table, provides the information that is related to a configuration setting.
Table 12-2 New custom table XAPPPROPS Column name STOREENT_ID Column type INTEGER NOT NULL Description The internal reference number that identifies the store entity. These columns and the NAME are a unique index. The internal reference number of the configuration setting. For concurrency control. Currently unused. The external identifier that identifies the configuration setting. These columns and the STOREENT_ID are a unique index. The value of the configuration setting. An administrative description of the configuration setting.
VALUE DESCRIPTION
VARCHAR(2000) VARCHAR(2000)
305
Description An identifier to define the type of data of the column VALUE. Valid entries are SimpleText, Code, Date, Time, DateTime, Color, Integer, Float, Email, or URL. A category description that is used to group configuration settings. Customizable. Customizable. Customizable. Customizable. Customizable. Customizable.
CATEGORY
CHARACTER(254)
To execute this script, we recommend that you use the Database Access servlet that is provided with WebSphere Commerce Developer: 1. From the J2EE perspective, go to the Server view, and start Start WebSphere Commerce Test Server. 2. After start-up is completed, open a Web browser, and call this URL: http://localhost/webapp/wcs/admin/servlet/db.jsp 3. Copy the script into the text area, and click Submit. If you execute this script the first time, you must omit the first three statements because they will lead to an error. Note: We did not test the SQL statements on Oracle. For later reference, store this SQL script in the CustomAppProperties-Server project: 1. Right-click the CustomAppProperties-Server project, and then select New Other. 2. Select Simple Folder, and then click Next. 3. In the Folder name field, type Scripts, and then click Finish. 4. Repeat Steps 1 to 3 to create a folder named Data under the folder Scripts.
306
5. Right-click the Scripts folder, and then select New Other. 6. Select Simple File, and then click Next. 7. In the File name field, type xappprops.ddl, and then click Finish. 8. Copy the code from Example 12-36 on page 304 into the file, and then save (Ctrl+S).
Figure 12-29 Data Service layer Configuration wizard, Service module selection
3. Click Next.
307
5. Click Finish. Now the code generation for CustomAppProperties-Server project is performed. 6. Update the CustomAppProperties-Server build path: a. Right-click the CustomAppProperties-Server project, and select Properties. b. Select Java Build Path. c. Under the Library tab, select Add External Jars, and add the following: Enablement-BaseComponentsData.jar from <WCDE_installdir>\wc.modules\ejbs\cloudscape
308
7. Organize the imports for the CustomAppProperties-Server project: a. Close all open Java files from the CustomAppProperties-Server project. a. In WebSphere Commerce Developer, open the Java perspective. b. Right-click the CustomAppProperties-Server\EJBModule folder, and select Source. c. Select Organize Imports. 8. Ensure that there are no compilation errors remaining. All generation steps are finished. You can now implement the service-specific logic and configuration.
309
Physical SDOs of the XAPPPROPS table are represented by the Xappprops class, which contains setter and getter methods to set each column of the table. Note: Keep in mind that the Xappprops physical SDO is only an internal representation of the XAPPPROPS table records. You can think in terms of a cached object or a detached object. The physical SDO is populated automatically by object-relational mapping service, but changes to the physical SDO does not result in a change of the XAPPPROPS table by its own. The classes and interfaces that are dedicated to the physical CustomAppProperties service module have names that start with CustomAppProperties. They can handle multiple physical SDOs and support multiple tables per service module. Object-relational metadata The object-relational metadata maps a physical SDO to the actual physical table in the WebSphere Commerce schema. The mapping is stored in the <WCDE_installdir>/xml/config/com.redbooks.commerce.customapppropertie s/wc-object-relational-metadata.xml file. If you open the file, you will find a <_config:table name="XAPPPROPS" ...> element that holds information about all columns of the XAPPPROPS table; furthermore, the foreign relationship to the STOREENT table is covered in a <_config:relationship ...> element. Query templates These templates map the WebSphere Commerce extended XPath notation expressions to one or more SQL template queries. These SQL template queries fetch the physical data from the database. In the following directory, there are two prebuilt query templates: <WCDE_installdir>/xml/config/com.redbooks.commerce.customappproperti es wc-query-CustomAppProperties-get.tpl.sample This query template contains queries to use with the Get verb. Because the generated query templates are not reflecting the updated logical and physical SDOs, you update this query template in a later step. wc-query-CustomAppProperties-update.tpl.sample This query template contains queries to use with the Process and Change verbs. Because the generated query templates are not reflecting the
310
updated logical and physical SDOs, you update this query template in a later step. Logical SDO to physical SDO mapping For each logical SDO, this mapping defines the physical SDO that contains the unique ID of the noun. The mapping is stored in the following file: <WCDE_installdir>/xml/config/com.redbooks.commerce.customapppropertie s/wc-business-object-mediator.xml Open the file to view the <_config:object> element. The attribute logicalType defines the logical SDO, and the attribute physicalType defines the physical SDO. Because the mapping file does not reflect the actual physical SDO type, you update this mapping file in a later step. The WebSphere Commerce Information Center has information that explains how to configure the Business object mediators in more detail: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tasks/tsdexposenewdata.htm Business object mediators Build logical SDOs from physical SDOs, and build physical SDOs from logical SDOs. The mediator classes are defined in the wc-business-object-mediator.xml file too, using the <_config:mediator> element and the <_config:part-mediator> sub-element. Because noun parts are not used in the CustomAppProperties service module, the definitions in <_config:part-mediator> have no effect. All mediators are stored in the package com.redbooks.commerce.customappproperties.facade.server.services.dataa ccess.bom.mediator: Read Mediators Build a logical SDO out of the physical SDO. The read mediator for Xappprops physical SDO is defined as ReadCustomAppPropertiesMediator class. Change Mediators Take a logical SDO as input and either Create, Update, or Delete the appropriate physical SDOs. The modified physical SDOs are then saved back to the database using the PhysicalDataContainer. The change mediator for the Xappprops physical SDO is defined in the ChangeCustomAppPropertiesMediator class.
311
Note: The read mediation always flows from physical SDO to logical SDO. You cannot have different mediators for the physical SDO depending on the logical SDO; therefore, you cannot map multiple nouns to a single table. EJB classes and interfaces The package com.redbooks.commerce.customappproperties.facade.server contains code for EJB communication between based on Session Beans. We recommend that you not change this code. BOD commands that implement verbs and actions The com.redbooks.commerce.customappproperties.facade.server.commands package contains BOD commands implementing Get, Process, and Change verbs and its actions. To learn more about developing the Business Logic layer using the BOD command framework, visit the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tasks/tsdcustomizesoasvc.htm Exceptions and language specific exception messages The com.redbooks.commerce.customappproperties.facade.server.exception package contains service module specific Exceptions. The following two packages contain message key definitions and language specific exception messages, which mediators and BOD commands use when throwing exceptions to the client: com.redbooks.commerce.customappproperties.logging com.redbooks.commerce.customappproperties.logging.properties In this scenario, you do not extend messages. Access control assets The following package contains a service module-specific protectable proxy class that is the bridge between any Java object and the policy manager access control engine that is part of WebSphere Commerce: com.redbooks.commerce.customappproperties.facade.server.authorization The protectable proxy class is configured in the <WCDE_installdir>/xml/config/com.redbooks.commerce.customapppropertie s/wc-component.xml file using the <_config:protectable-proxy> sub-element of the <_config:authorization-configuration> element. In this scenario, you implement access control.
312
CustomAppProperties service module meta data The metadata configuration is stored in the wc-component.xml file too. The <_config:database> element defines the JNDI name to the WebSphere Commerce database, while the Data Service is configured with the <_config:dataservice> element, which defines the data mediator type and the metadata initialization class. The com.redbooks.commerce.customappproperties.facade.server.metadata package contains the metadata initialization class CustomAppPropertiesMetadata that provides configuration information for the Data Service layer. Because the CustomAppPropertiesMetadata class does not reflect actual CustomAppProperties service module settings, you update this class in the next step.
12.4.8 Implementing the Business Object Mediators to mediate logical and physical SDOs
Mediators are needed for each changeable noun part. A noun part is either a part of a noun, or by definition, can be a noun itself.
313
To simplify development and maintenance of mediators, one noun can be sectioned into multiple changeable parts. We recommend that you use one mediator for each complex element in the noun, excluding any user data elements. In this scenario, we do not use noun parts. The CustomAppProperties noun is the only noun that we work on. To provide a better understanding on noun parts, imagine, we enhance the CustomAppProperties noun with a noun part that is holding a list of additional attributes for every configuration setting. These attributes could be persisted in an additional table XAPPPRATTR. In this case you would need additional mediators to read and update the configuration setting attribute noun part. Figure 12-31 outlines the steps.
CustomAppProperties-Server
Service module configuration Business object mediator configuration Value mapping Business Object Mediators Read mediators Change mediators
Figure 12-31 Implementing Business Object Mediators and updating the service module configuration
314
315
5. The buildNoun() method has to fulfill three tasks: Build the CustomAppProperties identifier with the UniqueID and the External Identifier. This is out sourced to the buildCustomAppPropertiesIdentifier() method. Set all of the attributes in the CustomAppProperties noun. Build and populate userData of the CustomAppProperties noun. This is out sourced to the buildUserData() method. Replace the buildNoun() method content between the Demo code Begin and Demo code End comments with the syntax in Example 12-37.
Example 12-37 buildNoun() method excerpt
CustomAppPropertiesType aCustomAppPropertiesLogicalDO = (CustomAppPropertiesType) aLogicalEntityType; Xappprops aCustomAppPropertiesPhysicalDO = (Xappprops) aPhysicalEntityType; // build the id type with uid and name buildCustomAppPropertiesIdentifier(aCustomAppPropertiesLogicalDO, aCustomAppPropertiesPhysicalDO); // set all attributes aCustomAppPropertiesLogicalDO.setDescription(aCustomAppPropertiesPhysicalDO .getDescription()); aCustomAppPropertiesLogicalDO .setCategory(aCustomAppPropertiesPhysicalDO.getCategory()); aCustomAppPropertiesLogicalDO.setValue(aCustomAppPropertiesPhysicalDO.getValue()); String valueType = aCustomAppPropertiesPhysicalDO.getValuetype(); if (valueType != null && !"".equals(valueType)) aCustomAppPropertiesLogicalDO.setValueType(ValueTypeEnum.get(valueType.trim())); // build the userdata from logical DataObject into Physical DataObject buildUserData(aCustomAppPropertiesLogicalDO, aCustomAppPropertiesPhysicalDO); 6. Implement the buildCustomAppPropertiesIdentifier() method. Replace the methods content between the Demo code Begin and Demo code End comments with the syntax in Example 12-38.
Example 12-38 buildCustomAppPropertiesIdentifier() method excerpt
// create the ID object and set to the logical data object CustomAppPropertiesIdentifierType aCustomAppPropertiesIdentifier = getCustomAppPropertiesFactory() .createCustomAppPropertiesIdentifierType(); aCustomAppPropertiesLogicalDO .setCustomAppPropertiesIdentifier(aCustomAppPropertiesIdentifier);
316
Xappprops aCustomAppPropertiesPhysicalDO = (Xappprops) aPhysicalEntityType; // set the uid in ID object aCustomAppPropertiesIdentifier.setUniqueID(aCustomAppPropertiesPhysicalDO .getXappprops_id()); // set the name in ID object CustomAppPropertiesExternalIdentifierType exId = getCustomAppPropertiesFactory() .createCustomAppPropertiesExternalIdentifierType(); aCustomAppPropertiesIdentifier.setExternalIdentifier(exId); exId.setName(aCustomAppPropertiesPhysicalDO.getName()); exId.setStoreentId(aCustomAppPropertiesPhysicalDO.getStoreent_id()); The methods now set the complete CustomAppPropertiesIdentifierType, which includes UniqueId and CustomAppPropertiesExternalIdentifierType, with Name and Storeent_id. 7. To enable userData, find and uncomment the buildUserData() method. Replace the methods content between the Demo code Begin and Demo code End comments with the syntax from Example 12-39.
Example 12-39 buildUserData() method
public void buildUserData(CustomAppPropertiesType aLogicalEntityType, Xappprops aPhysicalEntity) throws AbstractApplicationException { final String METHODNAME = "buildUserData"; if (LoggingHelper.isEntryExitTraceEnabled(LOGGER)) { LOGGER.entering(CLASSNAME, METHODNAME, new Object[] { aLogicalEntityType, aPhysicalEntity }); } Map attributes = new HashMap(); populateNameValuePairType(attributes, aLogicalEntityType, aPhysicalEntity); UserDataType userData = getCommerceFoundationFactory().createUserDataType(); aLogicalEntityType.setUserData(userData); Iterator anIter = attributes.entrySet().iterator(); while (anIter.hasNext()) { Map.Entry anEntry = (Map.Entry) anIter.next(); if (anEntry.getKey() != null && anEntry.getValue() != null) { aLogicalEntityType.getUserData().getUserDataField().put( anEntry.getKey().toString(), anEntry.getValue().toString()); } } if (LoggingHelper.isEntryExitTraceEnabled(LOGGER)) { LOGGER.exiting(CLASSNAME, METHODNAME);
317
} } The code copies all userData properties from the physical SDO to the logical SDO using the userData field in CustomAppProperties noun. 8. Organize the imports for the ReadCustomAppPropertiesMediator class: a. Open the Java perspective in WebSphere Commerce Developer. b. Right-click the ReadCustomAppPropertiesMediator class, and select Source. c. Select Organize Imports. 9. Add the correct throws statement to the buildNoun() method: a. Navigate to the buildNoun() method. b. Replace the statement throws BusinessObjectMediatorException with the statement throws AbstractApplicationException 10.Save file. (Ctrl+s)
318
Table 12-3 ChangeCustomAppPropertiesMediator methods to be implemented Method validateCreate() Description Checks if the physical object can be created from a given logical noun. Checks if: The object to be created is present in the physical data container. The external identifier fields are not empty or null. Mandatory fields are present. Optionally performs other checks. If one of the checks fail, a validation error is added to the return list. Implementation Checks for already existing objects and checks on the prebuilt Name fields. Other checks are up to you.
319
Method create()
Description Creates the physical entity that corresponds to the logical noun. Uses physical data containers to create physical objects and to get a new primary key from the key manager. Sets all fields from the logical noun to physical object. Calls the updateNameValuePairTy pe() method to set the userData field. You do not need to call the save() method on the physical data container because the BOD command framework does it.
Implementation The pre-built code contains most of the code. You must provide proper field population from the logical noun to the physical object. Use the example code as a guide.
updateNameValuePairType ()
Updates the physical CustomAppProperties object with the user data that is set in the logical CustomAppProperties noun. The name value pair mapping is set in the wc-businessobject-mediat or.xml file.
320
Method validateDelete()
Description Checks if the physical object can be deleted represented by the given logical noun. Checks if the object to be removed is present in the physical data container. If the entity is not present, a validation error is added to the return list.
Implementation Checks if the present object is pre-built. Other checks are up to you.
delete()
Deletes the logical noun. The delete action on the logical noun is copied to the physical implementation. Deleting the logical noun can result in an update of the physical entity in the form of a mark for delete or can result in an actual delete from the physical data store. To physically remove the object, call the removePhysicalObject() method on the physical data container.
321
Method findPhysicalEntity()
Description Finds the physical entity in the physical data container. Because the physical data container might return a list of physical entities after initialization, the correct entity needs to be identified. Typically this is done by traversing all entities and comparing them by UniqueID with the given logical noun. Remember, the physical entities are only cached objects, so iteration is performed in Java code, not on the database. This method is used by validateDelete() and delete().
Implementation Prebuilt code uses a simple UniqueID comparison to find a physical entity that correlates to a given logical noun. You can implement additional comparisons to identify the correct physical entity.
322
Description Initializes the physical data container for this mediator. Obtains the physical entities from the list of given logical nouns. The data from nouns that are present in the list is used to fetch the physical entities with the help of queries that are configured with the Data Service layer. This method is called from the BOD command framework every time the superclass method getPhysicalDataContainer () is called, for example, in the findPhysicalEntity() method.
resolveByExternalIdentifier ()
Resolves the noun part, which has only the external identifier. The noun parts with external identifiers should be resolved to set the UniqueID in the noun. This method is called the fromvalidateCreate() method.
The pre-built code does not reflect the updated external identifier containing of Name and Storeent_id field. You must update the code appropriately.
validateChange()
Because the Change verb is implemented by the ChangeCustomAppPrope rtiesBasePartMediator class this method is not implemented.
No action.
323
Method change()
Description Because the Change verb is implemented by the ChangeCustomAppPrope rtiesBasePartMediator class this method is not implemented.
Implementation No action.
324
Table 12-4 ChangeCustomAppPropertiesBasePartMediator methods to implement Method validateChange() Description Checks if the physical object can be updated from the modified logical noun. Checks if: The object to be updated is present in the physical data container. The external identifier fields are not empty or null. Mandatory fields are present. Optionally performs other checks. If the physical object cannot be updated, the appropriate error exception is added to the list of exceptions to be returned to the caller. update() Updates the physical data container with the modified logical noun. Sets all fields from logical noun to physical object. Calls the updateNameValuePairTy pe() method to set userData field. You do not need to call the physical data container because the BOD command framework does this. Pre-built code contains most of the code. Provide proper field population from the logical noun to the physical object. Use the example code as a guide. Implementation Checks for already existing object and checks on Name field are pre-built. Update the check for existing physical objects, and add the Storeent_id field to the external identifier. Other checks are up to you.
325
Description Updates the physical CustomAppProperties object with the user data set in the logical CustomAppProperties noun. The name value pair mapping is set in the wc-businessobject-media tor.xml file. This method returns the physical SDO from the physical data container that was created in the ChangeCustomAppProp ertiesMediator by calling the getParentPhysicalDataC ontainer() method. Function works similar to findPhysicalEntity() method in the ChangeCustomAppProp ertiesMediator. Is used by the validateChange() and update() methods.
findPhysicalEntity()
Pre-built code uses a simple UniqueID comparison to find the physical entity that correlates to a given logical noun and noun part. You can implement additional comparisons to identify the correct physical entity.
initializePhysicalDataCon tainer()
Initializes the physical data container for this mediator. It currently performs no actions because the physical data container from the ChangeCustomAppProp ertiesMediator is used in this mediator. Performs no actions because the ChangeCustomAppProp ertiesMediator does the resolve.
resolveByExternalIdentifi er()
326
Method validateCreate()
Description Performs no checking because the ChangeCustomAppProp ertiesMediator does the validation. Performs no actions because the ChangeCustomAppProp ertiesMediator creates the nouns. Performs no checking because the ChangeCustomAppProp ertiesMediator does the validation. Performs no actions because the ChangeCustomAppProp ertiesMediator deletes the nouns.
create()
validateDelete()
delete()
327
7. Add an additional constant NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_STOREENTID that represents the Storeent_id field of the noun. 8. Add an additional constant NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_CATEGORY that represents the Category field of the noun. 9. Delete or comment out the XPATH_CUSTOMAPPPROPERTIES_NAME constant. We do not use this Xpath expression because it does not reflect the Storeent_id external identifier part. 10.Delete or comment out the XPATH_CUSTOMAPPPROPERTIES constant, because we do not use this Xpath expression. 11.Add an additional constant XPATH_EMPTY_CUSTOMAPPPROPERTIES that represents the Xpath statement to access an undefined noun. The Process and Change BOD commands use this XPath expression. For Process, action Delete processing, and for Change processing, the provided logical noun is used to identify the physical object, which Business Object Mediators do. For Process, action Create no identification is needed (except for checking of existing object with same identifier) because in this case a new physical object is created. 12.Add an additional constant XPATH_CUSTOMAPPPROPERTIES_EXTERNAL_ID that represents the Xpath statement to read a noun by providing external identifiers. 13.Add an additional constant XPATH_CUSTOMAPPPROPERTIES_BY_CATEGORY_BY_STOREENT_ID that represents the Xpath statement to read a noun by a given Category and the Storeent_id. 14.Add an additional constant XPATH_PATTERN_CUSTOMAPPPROPERTIES_SEARCH_BY_STOREENT_ID that represents the Xpath statement to search nouns by a given Storeent_id using the search() expression builder. 15.Add an additional constant XPATH_CUSTOMAPPPROPERTIES_BY_STOREENTID that represents the Xpath statement to read all nouns by a given Storeent_id.
Example 12-40 CustomAppPropertiesFacadeConstants class
package com.redbooks.commerce.customappproperties.facade; /** * This class defines the constants used to interact with the CustomAppProperties * facade. */ public class CustomAppPropertiesFacadeConstants { // =============================================================================
328
// The following is global constants names associated with the component. // ============================================================================= /** * The component name for the CustomAppProperties component. */ public static final String COMPONENT_NAME = "com.redbooks.commerce.customappproperties"; // ============================================================================= // The following is a predefined set of access profiles provided by the // component. // ============================================================================= /** * This constants represents an access profile that returns all * information about the noun being returned. */ public static final String ACCESS_PROFILE_ALL_INFORMATION = "Redbooks_All"; /** * This constants if the default access profile when one is not specified. * The default access profile is the all access profile. */ public static final String ACCESS_PROFILE_DEFAULT = ACCESS_PROFILE_ALL_INFORMATION; /** * Access profile for updating a noun */ public static final String ACCESS_PROFILE_UPDATE = "Redbooks_Update"; /** * Access profile for resolving a noun's ID */ public static final String ACCESS_PROFILE_ID_RESOLVE = "Redbooks_IdResolve"; /** * Access profile for reading category related information from the noun */ public static final String ACCESS_PROFILE_CATEGORY = "Redbooks_Category"; /** * Access profile for noun search */ public static final String ACCESS_PROFILE_SEARCH = "Redbooks_Search";
329
// ============================================================================= // The following is a predefined set of XPATH provided by the component. // ============================================================================= /** * The constanst for closing the <code>Xpath</code> with a backet. */ public static final String CLOSE_XPATH = "]"; /** * The constanst for closing the <code>Xpath</code> with 2 backets. */ public static final String DOUBLE_CLOSE_XPATH = ")]"; /** * The attribute name of UID in noun CustomAppProperties. */ public static final String NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_UID = "UniqueID"; /** * The <code>Xpath</code> of CustomAppProperties with UID. */ public static final String XPATH_CUSTOMAPPPROPERTIES_UID = "/CustomAppProperties/CustomAppPropertiesIdentifier[(" + NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_UID + "=" + CustomAppPropertiesFacadeConstants.DOUBLE_CLOSE_XPATH; /** * The attribute name of Name in noun CustomAppProperties. */ public static final String NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_NAME = "Name"; /** * The attribute name of Storeent_id in noun CustomAppProperties. */ public static final String NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_STOREENTID = "Storeent_id"; /** * The attribute name of Category in noun CustomAppProperties. */ public static final String NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_CATEGORY = "Category";
330
/** * The <code>Xpath</code> for an empty, unresolved CustomAppProperties noun. */ public static final String XPATH_EMPTY_CUSTOMAPPPROPERTIES = "/CustomAppProperties[1]"; /** * The <code>Xpath</code> of CustomAppProperties with external ID. */ public static final String XPATH_CUSTOMAPPPROPERTIES_EXTERNAL_ID = "/CustomAppProperties/CustomAppPropertiesIdentifier/ExternalIdentifier[(" + NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_NAME + "= and " + NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_STOREENTID + "=" + DOUBLE_CLOSE_XPATH; /** * The <code>Xpath</code> of CustomAppProperties with Category and * Storeent_id. */ public static final String XPATH_CUSTOMAPPPROPERTIES_BY_CATEGORY_BY_STOREENT_ID = "/CustomAppProperties[(" + NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_CATEGORY + "=) and CustomAppPropertiesIdentifier[ExternalIdentifier[(" + NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_STOREENTID + "=" + DOUBLE_CLOSE_XPATH + CLOSE_XPATH + CLOSE_XPATH; /** * The <code>Xpath</code> of searching CustomAppProperties for a given * Storeent_id. */ public static final String XPATH_PATTERN_CUSTOMAPPPROPERTIES_SEARCH_BY_STOREENT_ID = "/CustomAppProperties[CustomAppPropertiesIdentifier[ExternalIdentifier[(" + NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_STOREENTID + "=" + DOUBLE_CLOSE_XPATH + CLOSE_XPATH + " and search()" + CLOSE_XPATH; /** * The <code>Xpath</code> of getting CustomAppProperties by Storeent_id.
331
*/ public static final String XPATH_CUSTOMAPPPROPERTIES_BY_STOREENTID = "/CustomAppProperties/CustomAppPropertiesIdentifier/ExternalIdentifier[(" + NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_STOREENTID + "=" + CustomAppPropertiesFacadeConstants.DOUBLE_CLOSE_XPATH; // ============================================================================= // The following is a list of actions that can be used for the process request. // ============================================================================= /** * Constant for the Process Create action on CustomAppProperties */ public static final String PROCESS_VERB_ACTION_CREATE_CUSTOMAPPPROPERTIES = "Create"; /** * Constant for the Process Delete action on CustomAppProperties */ public static final String PROCESS_VERB_ACTION_DELETE_CUSTOMAPPPROPERTIES = "Delete"; } Note: Declare all constants as public static final and be of type String.
CustomAppPropertiesType aCustomAppProperties = (CustomAppPropertiesType) aNoun; // check the name (external id) if (aCustomAppProperties.getCustomAppPropertiesIdentifier() != null && aCustomAppProperties.getCustomAppPropertiesIdentifier() .getExternalIdentifier() != null && aCustomAppProperties.getCustomAppPropertiesIdentifier() .getExternalIdentifier().getName() != null) {
332
String customapppropertiesName = aCustomAppProperties .getCustomAppPropertiesIdentifier().getExternalIdentifier().getName(); if (customapppropertiesName == null || customapppropertiesName.length() == 0) { // name cannot be empty ApplicationError validateError = new ApplicationError( ApplicationError.TYPE_GENERIC_ERROR, CustomAppPropertiesApplicationMessageKeys._APP_CUSTOMAPPPROPERTIES_NAME_EMPTY, null, LOGGER.getResourceBundleName()); validationErrors.add(validateError); } else { if (LoggingHelper.isTraceEnabled(LOGGER)) { LOGGER.logp(Level.FINE, CLASSNAME, METHODNAME, "validate customappproperties name: " + customapppropertiesName); } // check for duplicate name existing in the database already if (resolveByExternalIdentifier(aNoun)) { ApplicationError validateError = new ApplicationError( ApplicationError.TYPE_GENERIC_ERROR, CustomAppPropertiesApplicationMessageKeys._APP_CUSTOMAPPPROPERTIES_NAME_ALREADY_EXIST S, new Object[] { customapppropertiesName }, LOGGER .getResourceBundleName()); validationErrors.add(validateError); } // check for name length else if (CustomAppPropertiesMetadata.invalidStringLength( customapppropertiesName, CustomAppPropertiesMetadata.CUSTOMAPPPROPERTIES_NAME_LENGTH_LIMIT)) { ApplicationError validateError = new ApplicationError( ApplicationError.TYPE_GENERIC_ERROR, CustomAppPropertiesApplicationMessageKeys._APP_CUSTOMAPPPROPERTIES_NAME_TOO_LONG, null, LOGGER.getResourceBundleName()); validationErrors.add(validateError); } } } else { // check name cannot be empty
333
ApplicationError validateError = new ApplicationError( ApplicationError.TYPE_GENERIC_ERROR, CustomAppPropertiesApplicationMessageKeys._APP_CUSTOMAPPPROPERTIES_NAME_EMPTY, null, LOGGER.getResourceBundleName()); validationErrors.add(validateError); } // Check description String strDescription = aCustomAppProperties.getDescription(); // check description's length if (CustomAppPropertiesMetadata.invalidStringLength(strDescription, CustomAppPropertiesMetadata.CUSTOMAPPPROPERTIES_DESCRIPTION_LENGTH_LIMIT)) { ApplicationError validateError = new ApplicationError( ApplicationError.TYPE_GENERIC_ERROR, CustomAppPropertiesApplicationMessageKeys._APP_CUSTOMAPPPROPERTIES_DESCRIPTION_TOO_LO NG, null, LOGGER.getResourceBundleName()); validationErrors.add(validateError); } 4. Navigate to the method create(). 5. Replace the create() method content between the Demo code Begin and Demo code End comments with Example 12-42.
Example 12-42 create() method excerpt
// Create the entity CustomAppPropertiesType logicalCustomAppProperties = (CustomAppPropertiesType) aNoun; PhysicalDataContainer pdc = getPhysicalDataContainer(); Xappprops physicalCustomAppProperties = (Xappprops) pdc.createPhysicalObject(null, Xappprops.class); // get thd next available uid and then insert into the physical dataobject Long uid = pdc.getNextPrimaryKey(Xappprops.class); physicalCustomAppProperties.setXappprops_id(uid.longValue()); logicalCustomAppProperties.getCustomAppPropertiesIdentifier().setUniqueID( uid.longValue()); if (LoggingHelper.isTraceEnabled(LOGGER)) { LOGGER.logp(Level.FINE, CLASSNAME, METHODNAME, "new CustomAppProperties unique id= " + uid); }
334
// set the name and storeent_id if (logicalCustomAppProperties.getCustomAppPropertiesIdentifier() != null && logicalCustomAppProperties.getCustomAppPropertiesIdentifier() .getExternalIdentifier() != null) { physicalCustomAppProperties.setName(logicalCustomAppProperties .getCustomAppPropertiesIdentifier().getExternalIdentifier().getName()); physicalCustomAppProperties.setStoreent_id(logicalCustomAppProperties .getCustomAppPropertiesIdentifier().getExternalIdentifier() .getStoreentId()); } // set String String String String all other attributes description = logicalCustomAppProperties.getDescription(); value = logicalCustomAppProperties.getValue(); valueType = logicalCustomAppProperties.getValueType().getName(); category = logicalCustomAppProperties.getCategory();
if (description != null) physicalCustomAppProperties.setDescription(description); if (value != null) physicalCustomAppProperties.setValue(value); if (valueType != null) physicalCustomAppProperties.setValuetype(valueType); if (category != null) physicalCustomAppProperties.setCategory(category); // update the user data field updateNameValuePairType(logicalCustomAppProperties, physicalCustomAppProperties); 6. Navigate to the validateDelete() method. 7. Replace the validateDelete() method content with Example 12-43.
Example 12-43 validateDelete() method
public List validateDelete(Object aNoun) throws DataMediatorException { final String METHODNAME = "validateDelete"; if (LoggingHelper.isEntryExitTraceEnabled(LOGGER)) { LOGGER.entering(CLASSNAME, METHODNAME, new Object[] { aNoun }); } List validationErrors = new ArrayList(); if (findPhysicalEntity(aNoun) == null) { ApplicationError validateError = new ApplicationError(
335
ApplicationError.TYPE_GENERIC_ERROR, CustomAppPropertiesApplicationMessageKeys._APP_CUSTOMAPPPROPERTIES_NOT_FOUND, new Object[] { aNoun }, LOGGER.getResourceBundleName()); validationErrors.add(validateError); } if (LoggingHelper.isEntryExitTraceEnabled(LOGGER)) { LOGGER.exiting(CLASSNAME, METHODNAME, validationErrors); } return validationErrors; } 8. Navigate to the delete() method. 9. Replace the delete() method content between the Demo code Begin and Demo code End comments with Example 12-44.
Example 12-44 delete() method excerpt
// Delete the entity; can either mean mark for delete, or delete CustomAppPropertiesType logicalCustomAppProperties = (CustomAppPropertiesType) aNoun; long customapppropertiesId = logicalCustomAppProperties .getCustomAppPropertiesIdentifier().getUniqueID(); // Get the physical data object with specific id Xappprops physicalCustomAppProperties = (Xappprops) findPhysicalEntity(aNoun); if (physicalCustomAppProperties != null) { if (LoggingHelper.isTraceEnabled(LOGGER)) { LOGGER.logp(Level.FINE, CLASSNAME, METHODNAME, "Delete Data Object CustomAppProperties with unqiue ID: " + customapppropertiesId); } getPhysicalDataContainer().removePhysicalObject(physicalCustomAppProperties); } else { if (LoggingHelper.isTraceEnabled(LOGGER)) { LOGGER.logp(Level.FINE, CLASSNAME, METHODNAME, "Data Object CustomAppProperties with unqiue ID: " + customapppropertiesId + " doesn't exist"); } } 10.Navigate to the findPhysicalEntity() method. 11.Replace the findPhysicalEntity() method content between the Demo code Begin and Demo code End comments with Example 12-45 on page 337.
336
Xappprops aPhysicalEntity = null; PhysicalDataContainer pdc = getPhysicalDataContainer(); // if the PhysicalDataContainer is not null, loop it and compare the uid to find the DataObject if (pdc != null) { CustomAppPropertiesType aLogicalCustomAppProperties = (CustomAppPropertiesType) aNoun; long customapppropertiesId = aLogicalCustomAppProperties .getCustomAppPropertiesIdentifier().getUniqueID(); if (LoggingHelper.isTraceEnabled(LOGGER)) { LOGGER.logp(Level.FINE, CLASSNAME, METHODNAME, "Find CustomAppProperties: " + customapppropertiesId); } List customapppropertiesList = pdc.getPhysicalObjects(); if (customapppropertiesList != null && customapppropertiesList.size() > 0) { Iterator customapppropertiesIter = customapppropertiesList.iterator(); while (customapppropertiesIter.hasNext()) { Xappprops tempPhysicalEntity = (Xappprops) customapppropertiesIter.next(); // compare id to get the Physical DataObject if (tempPhysicalEntity.getXappprops_id() == customapppropertiesId) { aPhysicalEntity = tempPhysicalEntity; break; } } } } if (LoggingHelper.isEntryExitTraceEnabled(LOGGER)) { LOGGER.exiting(CLASSNAME, METHODNAME, aPhysicalEntity); } return aPhysicalEntity; 12.Navigate to the initializePhysicalDataContainer() method. 13.Replace the initializePhysicalDataContainer() method content between the Demo code Begin and Demo code End comments with Example 12-46.
Example 12-46 initializePhysicalDataContainer() method excerpt
337
// Get a list of extenal IDs from the nouns List customapppropertiesIdList = new ArrayList(); Iterator customapppropertiesNounIterator = aListNoun.iterator(); while (customapppropertiesNounIterator.hasNext()) { CustomAppPropertiesType aCustomAppPropertiesType = (CustomAppPropertiesType) customapppropertiesNounIterator .next(); if (aCustomAppPropertiesType.getCustomAppPropertiesIdentifier() != null) { long uniqueID = aCustomAppPropertiesType.getCustomAppPropertiesIdentifier() .getUniqueID(); customapppropertiesIdList.add(String.valueOf(uniqueID)); } } if (customapppropertiesIdList.size() > 0) { // if there is one or more noun, construct the query to get the physical data // containers SelectionCriteria query = new SelectionCriteria(); query .setXPathKey(CustomAppPropertiesFacadeConstants.XPATH_CUSTOMAPPPROPERTIES_UID); query.setAccessProfile(CustomAppPropertiesFacadeConstants.ACCESS_PROFILE_UPDATE); query .setXPathParameter(new RelationalExpression( CustomAppPropertiesFacadeConstants.NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_UID, customapppropertiesIdList)); physicalEntities = CustomAppPropertiesMetadata.getDataServiceFacade() .getPhysicalDataContainer(query); } else { //if no noun exist, return an empty PhysicalDataContainer physicalEntities = CustomAppPropertiesMetadata.getDataServiceFacade() .getEmptyPhysicalDataContainer(); } if (LoggingHelper.isEntryExitTraceEnabled(LOGGER)) { LOGGER.exiting(CLASSNAME, METHODNAME, physicalEntities); } return physicalEntities; 14.Remove or comment out the return statement after the Demo code End comment. 15.Navigate to the resolveByExternalIdentifier() method.
338
16.Replace the resolveByExternalIdentifier() method content between the Demo code Begin and Demo code End comments with Example 12-47.
Example 12-47 resolveByExternalIdentifier() method excerpt
boolean resolveStatus = false; long uniqueId; String name = null; long storeent_id = 0; CustomAppPropertiesType customapppropertiesNoun = (CustomAppPropertiesType) aNoun; // get the uid and see whether it exists if (customapppropertiesNoun.getCustomAppPropertiesIdentifier() != null) { uniqueId = customapppropertiesNoun.getCustomAppPropertiesIdentifier() .getUniqueID(); } if (LoggingHelper.isTraceEnabled(LOGGER)) { LOGGER.logp(Level.FINE, CLASSNAME, METHODNAME, "need to resolve noun"); } // get the external id if (customapppropertiesNoun.getCustomAppPropertiesIdentifier() != null && customapppropertiesNoun.getCustomAppPropertiesIdentifier() .getExternalIdentifier() != null) { name = customapppropertiesNoun.getCustomAppPropertiesIdentifier() .getExternalIdentifier().getName(); storeent_id = customapppropertiesNoun.getCustomAppPropertiesIdentifier() .getExternalIdentifier().getStoreentId(); } if (name != null) { // construct selectionCriteria to find the ID from extID SelectionCriteria query = new SelectionCriteria(); query .setXPathKey(CustomAppPropertiesFacadeConstants.XPATH_CUSTOMAPPPROPERTIES_EXTERNAL_ID ); query .setAccessProfile(CustomAppPropertiesFacadeConstants.ACCESS_PROFILE_ID_RESOLVE); query .setXPathParameter(new RelationalExpression(
339
CustomAppPropertiesFacadeConstants.NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_NAME, name)); query .setXPathParameter(new RelationalExpression( CustomAppPropertiesFacadeConstants.NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_STOREENTID , String.valueOf(storeent_id))); PhysicalDataContainer physicalEntites = CustomAppPropertiesMetadata .getDataServiceFacade().getPhysicalDataContainer(query); // if find the physical object of particular extID if (physicalEntites.getPhysicalObjects().size() > 0) { resolveStatus = true; Xappprops customapppropertiesEntity = (Xappprops) physicalEntites .getPhysicalObjects().get(0); // set the id back to the Logical Business Object customapppropertiesNoun.getCustomAppPropertiesIdentifier().setUniqueID( customapppropertiesEntity.getXappprops_id()); if (LoggingHelper.isTraceEnabled(LOGGER)) { LOGGER.logp(LoggingHelper.DEFAULT_TRACE_LOG_LEVEL, CLASSNAME, METHODNAME, "The external identifier " + customapppropertiesEntity.getXappprops_id() + " into the noun"); } } // if the external id is null } else { if (LoggingHelper.isTraceEnabled(LOGGER)) { LOGGER.logp(LoggingHelper.DEFAULT_TRACE_LOG_LEVEL, CLASSNAME, METHODNAME, "The external identifier is missing from the noun"); } } if (LoggingHelper.isEntryExitTraceEnabled(LOGGER)) { LOGGER.exiting(CLASSNAME, METHODNAME, new Boolean(resolveStatus)); } return resolveStatus; 17.Remove or comment out the return statement after the Demo code End comment.
340
18.After you update the code, you might find some compilation errors because of unhandled exceptions. Use the Eclipse Quick Fix function to update the throw statement of methods as proposed. 19.Organize the imports for the ChangeCustomAppPropertiesMediator class: a. Open the Java perspective in WebSphere Commerce Developer. b. Right-click the ChangeCustomAppPropertiesMediator class, and select Source. c. Select Organize Imports. 20.Save the file (Ctrl+S).
// check whether the noun exists by uid Xappprops physicalCustomAppProperties = (Xappprops) findPhysicalEntity(aNoun, aNounPart); if (physicalCustomAppProperties != null) { CustomAppPropertiesType logicalCustomAppProperties = (CustomAppPropertiesType) aNoun; // Check whether the name is valid if (logicalCustomAppProperties.getCustomAppPropertiesIdentifier() != null && logicalCustomAppProperties.getCustomAppPropertiesIdentifier() .getExternalIdentifier() != null && logicalCustomAppProperties.getCustomAppPropertiesIdentifier() .getExternalIdentifier().getName() != null && !logicalCustomAppProperties.getCustomAppPropertiesIdentifier() .getExternalIdentifier().getName().equals( physicalCustomAppProperties.getName())) { String name = logicalCustomAppProperties.getCustomAppPropertiesIdentifier() .getExternalIdentifier().getName(); long storeent_id = logicalCustomAppProperties .getCustomAppPropertiesIdentifier().getExternalIdentifier()
341
.getStoreentId(); if (name == null || name.length() == 0) { // name cannot be empty ApplicationError validateError = new ApplicationError( ApplicationError.TYPE_GENERIC_ERROR, CustomAppPropertiesApplicationMessageKeys._APP_CUSTOMAPPPROPERTIES_NAME_EMPTY, null, LOGGER.getResourceBundleName()); validationErrors.add(validateError); } else { // check whether the physical data object with the same name and storeent_id exists already if (traceEnabled) { LOGGER.logp(Level.FINE, CLASSNAME, METHODNAME, "validate CustomAppProperties name: " + name); } SelectionCriteria selectionCriteria = new SelectionCriteria(); selectionCriteria .setXPathKey(CustomAppPropertiesFacadeConstants.XPATH_CUSTOMAPPPROPERTIES_EXTERNAL_ID ); selectionCriteria .setAccessProfile(CustomAppPropertiesFacadeConstants.ACCESS_PROFILE_ID_RESOLVE); selectionCriteria .setXPathParameter(new RelationalExpression( CustomAppPropertiesFacadeConstants.NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_NAME, name)); selectionCriteria .setXPathParameter(new RelationalExpression( CustomAppPropertiesFacadeConstants.NOUN_ATTRIBUTE_NAME_CUSTOMAPPPROPERTIES_STOREENTID , String.valueOf(storeent_id))); PhysicalDataContainer pdc = CustomAppPropertiesMetadata .getDataServiceFacade() .getPhysicalDataContainer(selectionCriteria); List physicalDOList = pdc.getPhysicalObjects(); // if there is physical data object exist if (physicalDOList != null && physicalDOList.size() != 0) { ApplicationError validateError = new ApplicationError(
342
ApplicationError.TYPE_GENERIC_ERROR, CustomAppPropertiesApplicationMessageKeys._APP_CUSTOMAPPPROPERTIES_NAME_ALREADY_EXIST S, new Object[] { name }, LOGGER.getResourceBundleName()); validationErrors.add(validateError); } else if (CustomAppPropertiesMetadata.invalidStringLength(name, CustomAppPropertiesMetadata.CUSTOMAPPPROPERTIES_NAME_LENGTH_LIMIT)) { // checked name length ApplicationError validateError = new ApplicationError( ApplicationError.TYPE_GENERIC_ERROR, CustomAppPropertiesApplicationMessageKeys._APP_CUSTOMAPPPROPERTIES_NAME_TOO_LONG, null, LOGGER.getResourceBundleName()); validationErrors.add(validateError); } } } // Check whether the description is valid // Check the length of the description if (CustomAppPropertiesMetadata.invalidStringLength(logicalCustomAppProperties .getDescription(), CustomAppPropertiesMetadata.CUSTOMAPPPROPERTIES_DESCRIPTION_LENGTH_LIMIT)) { ApplicationError validateError = new ApplicationError( ApplicationError.TYPE_GENERIC_ERROR, CustomAppPropertiesApplicationMessageKeys._APP_CUSTOMAPPPROPERTIES_DESCRIPTION_TOO_LO NG, null, LOGGER.getResourceBundleName()); validationErrors.add(validateError); } } 4. Navigate to the update() method. 5. Replace the update() method content between the Demo code Begin and Demo code End comments with Example 12-49.
Example 12-49 update() method excerpt
Xappprops physicalCustomAppProperties = (Xappprops) findPhysicalEntity(aNoun, aNounPart); // set values from logical data object into the query and run it
343
// set the name and storeent_id if ((logicalCustomAppProperties.getCustomAppPropertiesIdentifier() != null) && (logicalCustomAppProperties.getCustomAppPropertiesIdentifier() .getExternalIdentifier() != null) && (logicalCustomAppProperties.getCustomAppPropertiesIdentifier() .getExternalIdentifier().getName() != null)) { physicalCustomAppProperties.setName(logicalCustomAppProperties .getCustomAppPropertiesIdentifier().getExternalIdentifier().getName()); physicalCustomAppProperties.setStoreent_id(logicalCustomAppProperties .getCustomAppPropertiesIdentifier().getExternalIdentifier() .getStoreentId()); } // set String String String String all other attributes description = logicalCustomAppProperties.getDescription(); value = logicalCustomAppProperties.getValue(); valueType = logicalCustomAppProperties.getValueType().getName(); category = logicalCustomAppProperties.getCategory();
if (description != null) physicalCustomAppProperties.setDescription(description); if (value != null) physicalCustomAppProperties.setValue(value); if (valueType != null) physicalCustomAppProperties.setValuetype(valueType); if (category != null) physicalCustomAppProperties.setCategory(category); // update the user data field updateNameValuePairType(logicalCustomAppProperties, physicalCustomAppProperties); 6. Navigate to the findPhysicalEntity() method. 7. Replace the findPhysicalEntity() method content between the Demo code Begin and Demo code End comments with Example 12-50
Example 12-50 findPhysicalEntity() method excerpt
Xappprops aPhysicalEntity = null; PhysicalDataContainer pdc = getParentPhysicalDataContainer(); // if the PhysicalDataContainer is not null, loop it and compare the uid to find the DataObject if (pdc != null) { CustomAppPropertiesType aLogicalCustomAppProperties = (CustomAppPropertiesType) aNoun;
344
long customapppropertiesId = aLogicalCustomAppProperties .getCustomAppPropertiesIdentifier().getUniqueID(); if (LoggingHelper.isTraceEnabled(LOGGER)) { LOGGER.logp(Level.FINE, CLASSNAME, METHODNAME, "Find CustomAppProperties: " + customapppropertiesId); } List customAppPropertiesList = pdc.getPhysicalObjects(); if (customAppPropertiesList != null && customAppPropertiesList.size() > 0) { Iterator customAppPropertiesIter = customAppPropertiesList.iterator(); while (customAppPropertiesIter.hasNext()) { Xappprops tempPhysicalEntity = (Xappprops) customAppPropertiesIter.next(); // compare id to get the Physical DataObject if (tempPhysicalEntity.getXappprops_id() == customapppropertiesId) { aPhysicalEntity = tempPhysicalEntity; break; } } } } if (LoggingHelper.isEntryExitTraceEnabled(LOGGER)) { LOGGER.exiting(CLASSNAME, METHODNAME, aPhysicalEntity); } return aPhysicalEntity; 8. Remove or comment out the return statement after the Demo code End comment. 9. After you update the code, you might find some compilation errors because of unhandled exceptions. Use the Eclipse Quick Fix Function to update the throw statement of methods as proposed. 10.Organize the imports for the ChangeCustomAppPropertiesBasePartMediator class: a. Open the Java perspective in WebSphere Commerce Developer. b. Right-click the ChangeCustomAppPropertiesBasePartMediator class, and select Source. c. Select Organize Imports. 11.Save the file (Ctrl+S).
345
12.4.9 Configuring the Data Service layer for the CustomAppProperties service module
Figure 12-32 outlines the next steps for customizing the new service module.
CustomAppProperties-Server
Service module configuration Business object mediator configuration Value mapping Business Object Mediators Read mediators Physical object persistence service Physical SDO Java classes Change mediators
Object-relational metadata
Query templates
To configure the Data Service layer: 1. Define two query templates: wc-query-CustomAppProperties-get.tpl A query template file that maps each get request XPath statement and access profile to a SQL template query. wc-query-CustomAppProperties-update.tpl A query template file that maps each change request XPath statement to a SQL template query. The pre-built sample query template files contain some sample queries and access profiles that are based on the assumption of the table name being equal to the component service name (CustomAppProperties), standard columns, and standard identifier. Neither the updated logical CustomAppProperties noun nor the used physical table XAPPPROPS are reflected, so update the template files in the upcoming steps.
346
Detailed information about query templates is available in the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/concepts/csdqtf.htm 2. Define the logical to physical SDO mapping. Defines the wc-business-object-mediator.xml file to relate logical nouns to physical data objects. Detailed information about the wc-business-object-mediator.xml file is available in the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tasks/tsdexposenewdata.htm 3. Update the CustomAppProperties service configuration (wc-component.xml). Detailed information about the wc-component.xml file is available in the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tasks/tsdextendconfig.htm
347
3. Review the section SYMBOL_DEFINITIONS, which defines the columns that are used in the subsequent queries. 4. Review the XPATH_TO_SQL_STATEMENT sections. There is a configuration for all XPath expressions that are used for Get requests that you already defined in the CustomAppPropertiesFacadeConstants class in the CustomAppProperties-Client project. 5. Review the ASSOCIATION_SQL_STATEMENT and PROFILE section. Using the search expression builder to perform parametric search queries requires a two-step query. In the following sections, we provide steps to implement a search on configuration settings: XPath-to-SQL statement /CustomAppProperties[CustomAppPropertiesIdentifier[ExternalIdentif ier[(Storeent_id=)]] and search()] The statement returns only primary keys that the association SQL statement uses. The search() expression is provided with parameters that form the search condition, when building the Get request. Important: For some reason, the search() expression always needs to be on the end of the XPath-to-SQL statement. Otherwise, the XPath parser cannot match the XPath-to-SQL statement from the query template file with the XPath expression from a Data Service layer request. Association SQL statement Redbooks_XAPPPROPS_AllFields The association statement returns all fields and uses the primary keys that are fetched by a XPath-to-SQL statement, which is defined by an access profile. Access profile Redbooks_Search The access profile combines a XPath-to-SQL statement and an association SQL statement. For more details about two-step queries, visit the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/concepts/csdqueryexec.htm
348
BEGIN_SYMBOL_DEFINITIONS <!-- The table for noun CustomAppProperties --> <!-- getting all columns in the table--> COLS:XAPPPROPS = XAPPPROPS:* <!-- getting uid column in the table--> COLS:XAPPPROPS_ID = XAPPPROPS:XAPPPROPS_ID <!-- getting name column in the table--> COLS:XAPPPROPS_NAME = XAPPPROPS:NAME <!-- getting STOREENT_ID column in the table--> COLS:XAPPPROPS_STOREENT_ID = XAPPPROPS:STOREENT_ID <!-- getting CATEGORY column in the table--> COLS:XAPPPROPS_CATEGORY = XAPPPROPS:CATEGORY END_SYMBOL_DEFINITIONS <!-- ============================================================================ --> <!-- AccessProfile: Redbooks_All --> <!-- Get the all information for CustomAppProperties with specified uid --> <!-- All access profile includes all attributes --> <!-- @param UniqueID Unique id of CustomAppProperties to retrieve. --> <!-- ============================================================================ --> BEGIN_XPATH_TO_SQL_STATEMENT name=/CustomAppProperties/CustomAppPropertiesIdentifier[(UniqueID=)]+Redbooks_All base_table=XAPPPROPS sql= SELECT XAPPPROPS.$COLS:XAPPPROPS$ FROM XAPPPROPS WHERE XAPPPROPS.Id = ?UniqueID? END_XPATH_TO_SQL_STATEMENT <!-- ============================================================================ --> <!-- AccessProfile: Redbooks_All --> <!-- Get the all information for CustomAppProperties with specfified external ID --> <!-- All access profile includes all the attributes --> <!-- @param Name Name (External ID) of CustomAppProperties to retrieve --> <!-- @param Storeent_id Storeent_id (External ID) of CustomAppProperties to retr --> <!-- ============================================================================ --> BEGIN_XPATH_TO_SQL_STATEMENT name=/CustomAppProperties/CustomAppPropertiesIdentifier/ExternalIdentifier[(Name= and Storeent_id=)]+Redbooks_All base_table=XAPPPROPS
349
sql= SELECT XAPPPROPS.$COLS:XAPPPROPS$ FROM XAPPPROPS WHERE XAPPPROPS.NAME = ?Name? AND XAPPPROPS.STOREENT_ID = ?Storeent_id? END_XPATH_TO_SQL_STATEMENT <!-- ============================================================================ <!-- AccessProfile: Redbooks_All <!-- Get the all information for CustomAppProperties by given Category and <!-- Storeent_id <!-- All access profile includes all the attributes <!-- @param Category Category of CustomAppProperties to retrieve <!-- @param Storeent_id Storeent_id (External ID) of CustomAppProperties to retr <!-- ============================================================================ BEGIN_XPATH_TO_SQL_STATEMENT name=/CustomAppProperties[(Category=) and CustomAppPropertiesIdentifier[ExternalIdentifier[(Storeent_id=)]]]+Redbooks_All base_table=XAPPPROPS sql= SELECT XAPPPROPS.$COLS:XAPPPROPS$ FROM XAPPPROPS WHERE XAPPPROPS.CATEGORY = ?Category? AND XAPPPROPS.STOREENT_ID = ?Storeent_id? END_XPATH_TO_SQL_STATEMENT <!-- ============================================================================ <!-- AccessProfile: Redbooks_Category <!-- Get the all information for CustomAppProperties by given Storeent_id <!-- Redbooks_Category access profile includes only basic information <!-- Used to create the list of configuration setting categories <!-- @param Storeent_id Storeent_id (External ID) of CustomAppProperties to retr <!-- ============================================================================ BEGIN_XPATH_TO_SQL_STATEMENT --> --> --> --> --> --> --> -->
350
SELECT XAPPPROPS.$COLS:XAPPPROPS_ID$, XAPPPROPS.$COLS:XAPPPROPS_NAME$, XAPPPROPS.$COLS:XAPPPROPS_STOREENT_ID$, XAPPPROPS.$COLS:XAPPPROPS_CATEGORY$ FROM XAPPPROPS WHERE XAPPPROPS.STOREENT_ID = ?Storeent_id? ORDER BYCATEGORY ASC END_XPATH_TO_SQL_STATEMENT <!-- ============================================================================ <!-- AccessProfile: None <!-- Get the UniqueID for CustomAppProperties by given search expression <!-- and Storeent_id <!-- @param search search expression of CustomAppProperties to retrieve <!-- @param Storeent_id Storeent_id (External ID) of CustomAppProperties to retr <!-- ============================================================================ BEGIN_XPATH_TO_SQL_STATEMENT --> --> --> --> --> --> -->
name=/CustomAppProperties[CustomAppPropertiesIdentifier[ExternalIdentifier[(Storeent_ id=)]] and search()] base_table=XAPPPROPS sql= SELECT XAPPPROPS.$COLS:XAPPPROPS_ID$ FROM XAPPPROPS,$ATTR_TBLS$ WHERE XAPPPROPS.STOREENT_ID = ?Storeent_id? AND ( $ATTR_CNDS$ ) END_XPATH_TO_SQL_STATEMENT <!-- ============================================================================ <!-- Association Statement <!-- Adds all XAPPPROPS fields to the resultant data graph. <!-- ============================================================================ BEGIN_ASSOCIATION_SQL_STATEMENT name=Redbooks_XAPPPROPS_AllFields base_table=XAPPPROPS additional_entity_objects=true sql= SELECT --> --> --> -->
351
XAPPPROPS.$COLS:XAPPPROPS$ FROM XAPPPROPS WHERE XAPPPROPS.XAPPPROPS_ID IN ($ENTITY_PKS$) END_ASSOCIATION_SQL_STATEMENT <!-- ============================================================================ <!-- AccessProfile: Redbooks_Search <!-- Search Profile using the Redbooks_XAPPPROPS_AllFields association statement <!-- ============================================================================ BEGIN_PROFILE name=Redbooks_Search BEGIN_ENTITY base_table=XAPPPROPS associated_sql_statement=Redbooks_XAPPPROPS_AllFields END_ENTITY END_PROFILE 6. Save the file. --> --> --> -->
352
2. Review the SYMBOL_DEFINITIONS section, which defines the columns that are used in the subsequent queries. 3. Review the XPATH_TO_SQL_STATEMENT sections. There is a configuration for all XPath expressions that are used for Process and Change requests, which you already defined in the CustomAppPropertiesFacadeConstants class, in the CustomAppProperties-Client project. The section contains: An expression to read all of the data that the UniqueID identified (XAPPPROPS.XAPPPROPS_ID). An expression to resolve the UniqueID by external identifiers (XAPPPROPS.NAME and XAPPPROPS.STOREENT_ID).
Example 12-52 Query template file wc-query-CustomAppProperties-update.tpl
BEGIN_SYMBOL_DEFINITIONS <!-- The table for noun CustomAppProperties --> <!-- getting all columns in the table--> COLS:XAPPPROPS = XAPPPROPS:* <!-- getting uid column in the table--> COLS:XAPPPROPS_ID = XAPPPROPS:XAPPPROPS_ID <!-- getting name column in the table--> COLS:XAPPPROPS_NAME = XAPPPROPS:NAME <!-- getting STOREENT_ID column in the table--> COLS:XAPPPROPS_STOREENT_ID = XAPPPROPS:STOREENT_ID END_SYMBOL_DEFINITIONS <!-- ============================================================================ <!-- AccessProfile: Redbooks_Update <!-- Get the all information for CustomAppProperties with specified uid <!-- All access profile includes all attributes <!-- @param UniqueID Unique id of CustomAppProperties to retrieve. <!-- ============================================================================ BEGIN_XPATH_TO_SQL_STATEMENT --> --> --> --> --> -->
353
XAPPPROPS.XAPPPROPS_ID = ?UniqueID? END_XPATH_TO_SQL_STATEMENT <!-- ============================================================================ --> <!-- AccessProfile: Redbooks_IdResolve --> <!-- Get the information for CustomAppProperties with specfified Name and <!-- Storeent_id for ID resolving. --> <!-- Access profile includes the attribute UniqueID, Name, and Storeent_id --> <!-- @param Name Name (External ID) of CustomAppProperties to retrieve --> <!-- @param Storeent_id Storeent_id (External ID) of CustomAppProperties to retr --> <!-- ============================================================================ --> BEGIN_XPATH_TO_SQL_STATEMENT name=/CustomAppProperties/CustomAppPropertiesIdentifier/ExternalIdentifier[(Name= and Storeent_id=)]+Redbooks_IdResolve base_table=XAPPPROPS sql= SELECT XAPPPROPS.$COLS:XAPPPROPS_ID$, XAPPPROPS.$COLS:XAPPPROPS_NAME$, XAPPPROPS.$COLS:XAPPPROPS_STOREENT_ID$ FROM XAPPPROPS WHERE XAPPPROPS.NAME = ?Name? and XAPPPROPS.STOREENT_ID = ?Storeent_id? END_XPATH_TO_SQL_STATEMENT
354
the comment line. However, this mapping also does not reflect the actual name of the physical type. 4. Replace the following mapping definition with the content in Example 12-53. <_config:object logicalType="..." physicalType="...">
Example 12-53 Logical to physical SDO mapping definition
355
3. Inside of the <_config:dataservice> element, add an <_config:mapping> element. Add the content of Example 12-54 as a sub-element to the <_config:dataservice> element.
Example 12-54 Column mapping definition for the search expression builder
<!-- Mapping for xappprops search --> <_config:mapping> <_config:key name="CustomAppProperties" /> <_config:basetable name="XAPPPROPS" useAllColumns="false"> <_config:columns name="NAME" caseSensitive="false" /> <_config:columns name="VALUE" caseSensitive="false" /> <_config:columns name="DESCRIPTION" caseSensitive="false" /> <_config:columns name="VALUETYPE" caseSensitive="true" /> <_config:columns name="CATEGORY" caseSensitive="false" /> </_config:basetable> </_config:mapping> 4. Review the added code. You enabled the listed columns for search, specifying the names and a flag to use or to not use a case-sensitive search. Building the search expression: When you build the search expression in a later step, use the exact values that you defined in the name attribute. 5. Save the file.
356
CustomAppProperties-Server acpload massload Business logic Get commands Change commands Process commands Sync commands
The BOD programming that was introduced in Feature Pack 3 uses four BOD processing patterns: Get, Change, Process, and Sync: BOD Get processing pattern The Business Object Document Get processing pattern describes the design pattern that searches for and retrieves data. In this scenario, we use pre-built Get commands to read CustomAppProperties nouns from the XAPPPROPS table. We do not cover the implementation of new Get commands in this scenario. BOD Process processing pattern The Business Object Document Process pattern performs a single action on a single noun. Based on the information within that noun, a Process controller will control the actions of the business logic. This Process controller reads common data across the actions within the request, instantiates the task command implementations for those actions, and executes them. In this scenario, we use pre-built Process commands to create and delete CustomAppProperties nouns in the XAPPPROPS table. The implementation of new Process commands is not necessary, and we do not cover them within this scenario. BOD Change processing pattern The business object document Change processing pattern adds, changes, or deletes a business object. The use of BOD processing commands enables
357
the Change requests to perform multiple actions in one request. While Process commands work on nouns, the Change commands work on noun parts; however, because Process processing pattern supports no update action, the update of a noun is implemented with a Change command too. In this scenario, we use pre-built Change commands to update existing CustomAppProperties nouns in the XAPPPROPS table. It is not necessary to implement new Change commands; therefore, we do not cover this subject within this scenario. BOD Sync processing pattern Systems that contain master data records to push out notifications when their data has changed use the Business Object Document Sync processing pattern. The Sync request contains the updated version of the business object, and the request is sent out to all other listening systems to update their version of the business object. In this scenario, we use no Sync processing pattern. Detailed information about BOD commands using the four BOD processing patterns are available in the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.developer.soa.doc/concepts/csddesignpatterns.htm
358
6. Open the GetCustomAppPropertiesCmd.java command interface, which contains the following methods: setGetCustomAppProperties() Sets the GetCustomAppProperties Business Object Document representing a request. It contains an XPath expression to identify the CustomAppProperties nouns to be fetched. getShowCustomAppProperties() Returns the Show Business Object Document, which represents the response to the Get request. Review the GetCustomAppPropertiesCmdImpl implementation class, which is located in GetCustomAppPropertiesCmdImpl.java. Amongst others, it provides the name of the Fetch task commands interface through the getFetchCommandInterface() method. 7. Open the FetchCustomAppPropertiesCmd.java command interface. This command interface contains the getCustomAppPropertiess() method that returns the list of CustomAppProperties nouns that were retrieved through the fetch based on an XPath expression. Optionally, review the FetchCustomAppPropertiesCmdImpl implementation class, which is located in FetchCustomAppPropertiesCmdImpl.java. 8. Review the InsertMoreCustomAppPropertiesDataCmd.java command interface. Sometimes you want to include more data in the nouns you return from a Get service. WebSphere Commerce provides a customization point called the InsertMoreData command, for this purpose. To learn more about modifying a Get service to use business logic to insert more data for a specific access profile, visit the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tasks/tsdworkmoredata.htm 9. Open the ProcessCustomAppPropertiesCmd.java command interface, which contains the following methods: setProcessCustomAppProperties() Sets the ProcessCustomAppProperties Business Object Document representing a request. It contains the process type that represents the action code and a list of CustomAppProperties nouns to perform the action. getAcknowledgeCustomAppProperties()
359
Returns the Acknowledge Business Object Document that represents the response to the specific Process request. Optionally, review the ProcessCustomAppPropertiesCmd implementation class, which is located in ProcessCustomAppPropertiesCmdImpl.java. 10.Review action-specific Process commands. The generated commands are more or less empty, acting as facade and extension point for your specific needs. You will find the following interfaces and implementation classes: ProcessCustomAppPropertiesActionCmd Common interface for all CustomAppProperties Process commands. ProcessCustomAppPropertiesDeleteActionCmdImpl Customizable Process command to execute the Delete action. ProcessCustomAppPropertiesCreateActionCmdImpl Customizable Process command to execute the Create action. 11.Review the Change commands. The Change processing pattern stipulates the splitting of the Change request into smaller tasks. Hence, the Change command calls a number of task commands to perform the several tasks. The pre-built default implementation, which is located at ChangeCustomAppPropertiesCmdImpl.java, contains methods to return task-command interface names for actions on noun parts, as well as a method to build the BOD Respond document that represents the response to the Change request. The following task commands are called: ChangeCustomAppPropertiesBasePartCmdImpl This command modifies the noun's basic attributes. A list of nouns and action expressions are passed to this command to act upon. ChangeCustomAppPropertiesBasePartCmdImpl This command is executed post the update of the nouns's basic attributes, hence after the execution of the Change mediators.
360
Luckily, all needed assets are already generated and ready to use. Register the CustomAppProperties BOD commands using the next steps: 1. Using Windows Explorer, navigate to the following directory: <WCDE_installdir>/xml/config/com.redbooks.commerce.customapppropertie s 2. Using a text editor, open the CustomAppProperties-cmdreg.xml file. 3. Review the existing content. The file is an XML file that is formatted for import by the WebSphere Commerce Mass Load utility. The commands are registered to be valid for the WebSphere Commerce instance, not being tied on a specific store. Notice the special syntax on some interface name attribute values, which take the format of: package.commandInterface+XPathExpression, such as interfacename="com.redbooks.commerce.customappproperties.facade.serv er.commands.ChangeCustomAppPropertiesPartPostActionCmd+/CustomAppPro perties[]" The BOD command framework interprets this special syntax. The syntax after the plus character + references an XPath key that is based on the CustomAppProperties logical model. When the client library API receives an XPath that is formatted in this way, there is logic to interpret which fetch command to call and where to place the values for the corresponding attributes in the XPath key. To review detailed information, visit the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/concepts/csdsoaget.htm To review a more complex example, visit the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tutorial/twvstep3.htm This example is regarding a Feature Pack 2 tutorial, but the command interface interpretation is covered in detail and is also valid for Feature Pack 3. 4. Open a command prompt. In Windows XP, click Start > Run. 5. Enter cmd.exe. 6. On the command prompt, navigate to <WCDE_installdir>/bin directory with cd <WCDE_installdir>\bin
361
7. Use massload.bat to import CustomAppProperties-cmdreg.xml into your WebSphere Commerce database. If you are using a Cloudscape database, ensure that no application has a connection to the WebSphere Commerce database, such as the WebSphere Commerce Test Server. 8. On one line, type: massload ..\xml\config\com.redbooks.commerce.customappproperties\CustomAppPro perties-cmdreg.xml Oracle and DB2 users need to adapt the import statement respectively. 9. Review the Mass Load utility logs in the <WCDE_installdir>\logs directory
362
This package contains the CustomAppProperties Facade Client, which provides common methods that are used as single point-of-contact to the CustomAppProperties service module. This class is a provider for a set of convenient Java APIs to represent particular CustomAppProperties operations or Java APIs for Web-based requests by transforming the name-value-pair request into the appropriate request BODs. This class contains a code template for a method that the JSTL-enabled JSP uses. The super class AbstractCustomAppPropertiesFacadeClient contains all code for the basic Get, Process, and Change operations that we use in our scenario, so no additional update of the CustomAppPropertiesFacadeClient class is needed. You will use the CustomAppPropertiesFacadeClient class when we build the JUnit-based test class.
363
To learn more about the access control of the BOD framework, visit the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.developer.soa.doc/concepts/csdaccesscontrol.htm Code generation has a pre-built access control import file to be executed with the acpload utility. In a preceding step, you copied this file from the <workspace_dir>/CustomAppProperties-Server/TODO-MoveItToWCServerXML Folder/xml/policies/xml file, which is in the CustomAppProperties-Server project, into the <WCDE_installdir>/xml/policies/xml directory. Complete the configuration, and execute the load process: 1. Using Windows Explorer, navigate to the <WCDE_installdir>/xml/policies/xml directory. 2. In a text editor, open the CustomAppProperties-access-control.xml file, and review the content of the generated file: Access profile access control <Action> elements define access profile references. It takes the notation of serviceModuleName.accessProfileName. According to Actions, Action Groups are defined by <ActionGroup> elements to group the Actions. Access Control Policies are defined by <Policy> element, which specifies, what Actions (defined by Action Groups) can be executed by which User Groups, which ascertains if a given user can execute a Get command to a requested access profile. The <PolicyGroup> element and its <PolicyGroupPolicy> sub-elements defines the eligibility of the Access Control Policies to the organizational structure. Because we removed unnecessary access profiles and created new access profiles, you must update the <Action> elements. Access control for actions on the noun <Action> elements define supported actions on the CustomAppProperties noun. It takes the notation of nounInterface.action. According to Actions, Action Groups are defined by <ActionGroup> elements to group the Actions. <Resource> elements define Resources to protect. As mentioned earlier, a Protectable Proxy class acts on behalf of the noun.
364
A <Policy> element now combines an Action Group, a Resource, and an User Group, which ascertains if a given user can execute a BOD command that represents an action request on the requested noun. Policy Group definitions are analogous. The generated access control configuration on actions and nouns meets our requirements. However, by default the configuration relates to the non-existing User Group CustomAppPropertiesManagers, so you either must create this User Group or change the configuration to an already existing User Group. In this scenario, you use an existing User Group. 3. Update the CustomAppProperties-access-control.xml file: a. Replace all access profile-related resources for access profiles Redbooks_Summary and Redbooks_Details with resources that are needed for access profiles Redbooks_Category and Redbooks_Search. i. Replace the following syntax: <Action Name="GetCustomAppProperties.Redbooks_Details" CommandName="GetCustomAppProperties.Redbooks_Details" /> <Action Name="GetCustomAppProperties.Redbooks_Summary" CommandName="GetCustomAppProperties.Redbooks_All" /> by <Action Name="GetCustomAppProperties.Redbooks_Category" CommandName="GetCustomAppProperties.Redbooks_Category" /> <Action Name="GetCustomAppProperties.Redbooks_Search" CommandName="GetCustomAppProperties.Redbooks_Search" /> ii. Replace all <ActionGroupAction> sub-elements of the <ActionGroup Name="CustomAppProperties-CustomAppProperties-AllUsers-Access ProfileActionGroup...> by <ActionGroupAction Name="GetCustomAppProperties.Redbooks_All"/> <ActionGroupAction Name="GetCustomAppProperties.Redbooks_Category"/>
365
<ActionGroupAction Name="GetCustomAppProperties.Redbooks_Search"/> iii. Remove the <ActionGroup Name="CustomAppProperties-CustomAppProperties-CustomAppProp ertiesManagers-AccessProfileActionGroup...>". We do not need to add an additional Action Group of access profiles because we want to grant read access of the CustomAppProperties noun to everyone. iv. Remove the <Policy Name="CustomAppProperties-CustomAppProperties-CustomAppProp ertiesManagers-AccessProfilePolicy"...>. v. Remove the <PolicyGroupPolicy> element with attribute Name="CustomAppProperties-CustomAppProperties-CustomAppProp ertiesManagers-AccessProfilePolicy". b. Limit the group of users that have the right to create, update, and delete CustomAppProperties. Only Site Administrators should have this authority. Therefore, you must update the access control further: i. Move the Action references of the add and delete actions to the <ActionGroup Name="CustomAppProperties-CustomAppProperties-AllUsers-Action Group"...> ii. Remove the empty <ActionGroup Name="CustomAppProperties-CustomAppProperties-CustomAppProp ertiesManagers-ActionGroup"...>. iii. Remove the <Policy Name="CustomAppProperties-CustomAppProperties-CustomAppProp ertiesManagers-CreatorPolicy"...>. iv. Remove the <PolicyGroupPolicy Name="CustomAppProperties-CustomAppProperties-CustomAppProp ertiesManagers-CreatorPolicy"...>. v. In the <Policy Name="CustomAppProperties-CustomAppProperties-AllUsers-Creator Policy"...>, change the UserGroup attribute to UserGroup="SiteAdministrators" and the Name attribute to Name="CustomAppProperties-CustomAppProperties-SiteAdministrato rs-CreatorPolicy" c. In the <PolicyGroupPolicy Name="CustomAppProperties-CustomAppProperties-AllUsers-CreatorPol icy"...> change Name attribute to
366
Name="CustomAppProperties-CustomAppProperties-SiteAdministratorsCreatorPolicy" d. Make sure that your CustomAppProperties-access-control.xml file has the same content of Example 12-55.
Example 12-55 CustomAppProperties access control
<?xml version="1.0" encoding="ISO-8859-1" standalone="no" ?> <!DOCTYPE Policies SYSTEM "../dtd/accesscontrolpolicies.dtd"> <Policies> <!-- defining supported access profiles --> <!-- ================================================ --> <Action Name="GetCustomAppProperties.Redbooks_All" CommandName="GetCustomAppProperties.Redbooks_All" /> <Action Name="GetCustomAppProperties.Redbooks_Category" CommandName="GetCustomAppProperties.Redbooks_Category" /> <Action Name="GetCustomAppProperties.Redbooks_Search" CommandName="GetCustomAppProperties.Redbooks_Search" /> <!-- defining supported actions --> <!-- ================================================ --> <!-- read action (Get request) --> <Action Name="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ype.read" CommandName="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppProp ertiesType.read" /> <!-- change action (Change request) --> <Action Name="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ype.change" CommandName="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppProp ertiesType.change" />
367
<!-- process actions (Process request) --> <Action Name="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ype.add" CommandName="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppProp ertiesType.add" /> <Action Name="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ype.delete" CommandName="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppProp ertiesType.delete" /> <!-- defining resouce category --> <!-- ================================================ --> <!-- category for CustomAppProperties --> <ResourceCategory Name="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ypeResourceCategory" ResourceBeanClass="com.redbooks.commerce.customappproperties.facade.server.authorizat ion.CustomAppPropertiesTypeProtectableProxy" /> <!-- defining action groups --> <!-- ================================================ --> <!-- access profiles for all users --> <ActionGroup Name="CustomAppProperties-CustomAppProperties-AllUsers-AccessProfileActionGroup" OwnerID="RootOrganization"> <ActionGroupAction Name="GetCustomAppProperties.Redbooks_All"/> <ActionGroupAction Name="GetCustomAppProperties.Redbooks_Category"/> <ActionGroupAction Name="GetCustomAppProperties.Redbooks_Search"/> </ActionGroup> <!-- all user action group which contains read and change actions --> <ActionGroup Name="CustomAppProperties-CustomAppProperties-AllUsers-ActionGroup" OwnerID="RootOrganization"> <ActionGroupAction Name="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ype.read"/>
368
<ActionGroupAction Name="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ype.change"/> <ActionGroupAction Name="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ype.add"/> <ActionGroupAction Name="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ype.delete"/> </ActionGroup> <!-- defining resource groups --> <!-- ================================================ --> <!-- the customappproperties protectable proxy --> <ResourceGroup Name="CustomAppProperties-CustomAppProperties-ResourceGroup" OwnerID="RootOrganization"> <ResourceGroupResource Name="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ypeResourceCategory"/> </ResourceGroup> <!-- defining policy --> <!-- ================================================ --> <!-- the all users access profile access control policy --> <Policy Name="CustomAppProperties-CustomAppProperties-AllUsers-AccessProfilePolicy" OwnerID="RootOrganization" UserGroup="AllUsers" ActionGroupName="CustomAppProperties-CustomAppProperties-AllUsers-AccessProfileAction Group" ResourceGroupName="AccessProfileResourceGroup" PolicyType="groupableStandard" /> <!-- all user creator access control policy --> <Policy Name="CustomAppProperties-CustomAppProperties-SiteAdministrators-CreatorPolicy" OwnerID="RootOrganization" UserGroup="SiteAdministrators" ActionGroupName="CustomAppProperties-CustomAppProperties-AllUsers-ActionGroup" ResourceGroupName="CustomAppProperties-CustomAppProperties-ResourceGroup" RelationName="creator" PolicyType="groupableStandard" />
369
<!-- defining policy groups --> <!-- ================================================ --> <PolicyGroup Name="ManagementAndAdministrationPolicyGroup" OwnerID="RootOrganization"> <!-- all user access profile --> <PolicyGroupPolicy Name="CustomAppProperties-CustomAppProperties-AllUsers-AccessProfilePolicy" PolicyOwnerID="RootOrganization" /> <!-- all user creator policy --> <PolicyGroupPolicy Name="CustomAppProperties-CustomAppProperties-SiteAdministrators-CreatorPolicy" PolicyOwnerID="RootOrganization" /> </PolicyGroup> </Policies> e. Save the file. 4. Load the CustomAppProperties-access-control.xml file: a. Open a command prompt. In Windows XP, click Start Run. b. Enter cmd.exe. c. On the command prompt, type cd <WCDE_installdir>\bin to navigate to the <WCDE_installdir>/bin directory. d. Use acpload.bat to import CustomAppProperties-access-control.xml into your WebSphere Commerce database. If you are using the Cloudscape database, ensure that no application has a connection to the WebSphere Commerce database, such as the WebSphere Commerce Test Server. e. Enter acpload CustomAppProperties-access-control.xml. f. Review the acpload utility log in the <WCDE_installdir>\logs\acpload.log file. If you need to troubleshoot access control, review the article Debugging access control in WebSphere Commerce on IBM developerWorks at: http://www.ibm.com/developerworks/websphere/library/techarticles/0805_c allaghan/0805_callaghan.html?ca=dnb-wce061908 The article does not cover access control in the BOD command framework especially, but it gives you advice on tracking problems with access control.
370
Figure 12-34 Implement unit tests using the CustomAppProperties client library
Before you use the test methods, first review the unit test project configuration. 1. Open WebSphere Commerce Developer. 2. Open the J2EE perspective.
371
3. In the Project Explorer view, open the CustomAppProperties-UnitTests project. 4. Navigate to the src/config/com.ibm.commerce.foundation package, which holds the generic client library configuration. 5. Open wc-config-mapping-registry.xml The unit test project uses the client library, and this file is part of the client library configuration. The <_config:filemapping> element defines the name of the configuration file, which is wc-component-client.xml. Review the information about deploying client libraries in the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tasks/twvdeployclientlibrary.htm 6. Navigate to the src/config/com.redbooks.commerce.customappproperties package, which holds the client library configuration that is specific to the CustomAppProperties service module. 7. Open wc-component-client.xml, which primarily defines the type and the target of the service invocation using the <_config:invocationbinding> element. The unit tests use remote invocation that employs SOAP Web services over an HTTP transport channel. The <_config:property name="url"...> element defines the Web service endpoint. Note: The URL points to http://localhost:81/... by default. If you run the unit test class, it will fail because WebSphere Commerce Test Server listens to port 80. Either, you have to change the value to http://localhost:80/..., or you must start the TCP/IP monitor listening on port 81 before. Note, that there are example files that contain configuration to access the service module by local and remote service invocation. Detailed information about testing a WebSphere Commerce service is provided in the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.component-services.doc/tasks/twvtestcs.htm
372
package com.redbooks.commerce.customappproperties.facade.client; import java.util.HashMap; import java.util.Map; import com.redbooks.commerce.util.XpathExpressionHelper; /** * Simple JavaBean to hold CustomAppProperties data */ public class CustomAppPropertiesData { /** * Refers to /CustomAppProperties/CustomAppPropertiesIdentifier/UniqueID * A value of -1 means 'not set' */ private long uniqueID = -1; /**
373
* Refers to /CustomAppProperties/CustomAppPropertiesIdentifier/ExternalIdentifier/Name */ private String name; /** * Refers to /CustomAppProperties/CustomAppPropertiesIdentifier/ExternalIdentifier/Storeent_id */ private int storeent_id; /** * Refers to /CustomAppProperties/Value */ private String value; /** * Refers to /CustomAppProperties/Description */ private String description; /** * Refers to /CustomAppProperties/Category */ private String category; /** * Refers to /CustomAppProperties/ValueType */ private String valueType; /** * Refers to /CustomAppProperties[(search())] */ private String searchExpression; public String getSearchExpression() { return searchExpression; } public void setSearchExpression(String searchExpression) { this.searchExpression = searchExpression; } public String getCategory() {
374
return category; } public void setCategory(String category) { this.category = category; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getStoreent_id() { return storeent_id; } public void setStoreent_id(int storeent_id) { this.storeent_id = storeent_id; } public long getUniqueID() { return uniqueID; } public void setUniqueID(long uniqueID) { this.uniqueID = uniqueID; } public String getValue() { return value; } public void setValue(String value) { this.value = value;
375
} public String getValueType() { return valueType; } public void setValueType(String valueType) { this.valueType = valueType; } /** * Returns the Bean as Map using field as key * String values will be enquoted. * @return Map The Bean */ public Map getAsMap() { Map theBean = new HashMap(); if (name != null) theBean.put("Name", XpathExpressionHelper.enquote(name)); if (value != null) theBean.put("Value", XpathExpressionHelper.enquote(value)); if (category != null) theBean.put("Category", XpathExpressionHelper.enquote(category)); if (valueType != null) theBean.put("ValueType", XpathExpressionHelper.enquote(valueType)); if (description != null) theBean.put("Description", XpathExpressionHelper.enquote(description)); if (uniqueID != -1) theBean.put("UniqueID", String.valueOf(uniqueID)); theBean.put("Storeent_id", String.valueOf(storeent_id)); if (searchExpression != null) theBean.put(XpathExpressionHelper.PARAM_NAME_SEARCH, searchExpression); return theBean; } } f. Save the file (Ctrl+s). g. Repeat steps b to e to create another class named XpathExpressionHelper. h. Open XpathExpressionHelper.java, and copy the content from Example 12-57 on page 377 into the file.
376
The main task of the XpathExpressionHelper is to add parameter values to the XPath expression. It replaces: param= with param=value search() with search(searchExpression)
package com.redbooks.commerce.util; import import import import import java.text.MessageFormat; java.util.Iterator; java.util.Map; java.util.regex.Matcher; java.util.regex.Pattern;
import com.ibm.commerce.foundation.client.util.oagis.SelectionCriteriaHelper; /** * Simple utility to set parameter values in a XPath expression */ public class XpathExpressionHelper { public public public public public public public public static static static static static static static static final final final final final final final final String String String String String String String String PARAM_BASE_PATTERN = "\\s*="; EQUAL_CHAR = "="; QUOTE_CHAR = "'"; CURLY_BRACKET_OPEN = "{"; CURLY_BRACKET_CLOSE = "}"; SEARCH_PATTERN = "search\\(\\)"; PARAM_NAME_SEARCH = "_SearchExpression_"; SEARCH_REPLACEMENT_PATTERN = "search({0})";
/** * Adds values to parameters of a Xpath expression. Works on all occurences. * @param xpath The unresolved Xpath expression template * @param params A Map containing of parameter names (String) and parameter values (String) * @return String The resolved Xpath Expression */ public static String resolveXpathExpression(String xpath, Map params) { Pattern pat; String replacementContent; String resolvedXpath = xpath; Iterator iter = params.keySet().iterator(); while (iter.hasNext()) { // Get the parameter name
377
String paramName = (String) iter.next(); // Compile the RegEx pattern to find parameters, depending on the parameter if (PARAM_NAME_SEARCH.equals(paramName)) { pat = Pattern.compile(SEARCH_PATTERN); // Define the replacement, e.g. // 'search()' --> 'search(someField=1012)' Object[] searchExpression = { (String) params.get(paramName) }; replacementContent = (new MessageFormat(SEARCH_REPLACEMENT_PATTERN)) .format(searchExpression); } else { pat = Pattern.compile(paramName + PARAM_BASE_PATTERN); // Define the replacement, e.g. // 'ID=' --> 'ID=10001' replacementContent = paramName + EQUAL_CHAR + (String) params.get(paramName); } // Apply RegEx pattern to all occurrences Matcher m = pat.matcher(resolvedXpath); resolvedXpath = m.replaceAll(replacementContent); } return resolvedXpath; } /** * Same as <code>resolveXpathExpression (String xpath, Map params)</code> * but builds complete Xpath including access profile * @param xpath The unresolved Xpath expression template * @param params A Map containing of parameter names (String) and parameter values (String) * @param accessProfile The name of the access profile * @return String The resolved Xpath Expression */ public static String resolveXpathExpression(String xpath, Map params, String accessProfile) { StringBuffer xpathExpr = new StringBuffer(); xpathExpr.append(CURLY_BRACKET_OPEN); xpathExpr.append(SelectionCriteriaHelper.STR_ACCESS_PROFILE_PARAMETER); xpathExpr.append(EQUAL_CHAR); xpathExpr.append(accessProfile); xpathExpr.append(CURLY_BRACKET_CLOSE); xpathExpr.append(resolveXpathExpression(xpath, params)); return xpathExpr.toString(); } /**
378
* Returns the paramter enquoted * @param arg The parameter to enquote * @return String The enquoted parameter */ public static String enquote(String arg) { return QUOTE_CHAR + arg + QUOTE_CHAR; } } i. Save the file (Ctrl+s).
package com.redbooks.commerce.customappproperties.facade.client; /** * This class represents a set of test cases for testing the * CustomAppProperties facade client. */ public class CustomAppPropertiesFacadeClientTest extends junit.framework.TestCase { /** * The user id to associate with unit tests requiring * SiteAdministrator privileges */ // TODO: modify the test username
379
private static final String SITE_ADMIN_USER_ID = "wcsadmin"; /** * The password of the user id for these unit tests. */ // TODO: modify the test password private static final String SITE_ADMIN_PASSWORD = "your_passwd"; /** * The reference to an existing store. */ // TODO: modify the store reference private static final int STOREENT_ID = 10101; /** * The client to access the CustomAppProperties service */ private CustomAppPropertiesFacadeClient iClient = null; /** * Create an instance of the CustomAppProperties Facade Client test case. * @param method The test method to execute. */ public CustomAppPropertiesFacadeClientTest(String method) { super(method); } /** * Set up any additional information required in order to run any test method. * As part of the setup, the client library will be instantiated for the test case. * @exception A problem with the setup process. * @see TestCase#setUp() */ protected void setUp() throws Exception { super.setUp(); } /** * Remove any data or setting related to running any test method. * @exception A problem with the tear down process. * @see TestCase#tearDown() */ protected void tearDown() throws Exception { super.tearDown();
380
} /** * Builds the CustomAppProperties noun from test data * @param testData The test data * @return CustomAppPropertiesType The created CustomAppProperties noun */ private CustomAppPropertiesType buildCustomAppPropertiesNoun( CustomAppPropertiesData testData) { // Create the noun CustomAppPropertiesType capNoun = CustomAppPropertiesFactory.eINSTANCE .createCustomAppPropertiesType(); // Create CustomAppProperties identifier from test data CustomAppPropertiesIdentifierType capID = CustomAppPropertiesFactory.eINSTANCE .createCustomAppPropertiesIdentifierType(); capNoun.setCustomAppPropertiesIdentifier(capID); // Use either UniqueID if available or external identifier otherwise if (testData.getUniqueID() != -1) { // set UniqueID to noun capID.setUniqueID(testData.getUniqueID()); } else { // set external identifier to noun CustomAppPropertiesExternalIdentifierType capExID = CustomAppPropertiesFactory.eINSTANCE .createCustomAppPropertiesExternalIdentifierType(); capExID.setName(testData.getName()); capExID.setStoreentId(testData.getStoreent_id()); capID.setExternalIdentifier(capExID); } // Insert CustomAppProperties data from test data capNoun.setCategory(testData.getCategory()); capNoun.setDescription(testData.getDescription()); capNoun.setValue(testData.getValue()); capNoun.setValueType(ValueTypeEnum.get(testData.getValue())); return capNoun; } /** * Constructs a facade client using an empty BusinessContext and * and the WebSphere Commerce SampleCallbackHandler */ private CustomAppPropertiesFacadeClient constructFacadeClient(String userId,
381
String password) { // construct the callback handler javax.security.auth.callback.CallbackHandler callbackHandler = new com.ibm.commerce.foundation.client.samples.security.auth.callback.SampleCallbackHandl erImpl( userId, password); // construct business context com.ibm.commerce.foundation.common.datatypes.BusinessContextType businessContext = com.ibm.commerce.foundation.common.datatypes.CommerceFoundationFactory.eINSTANCE .createBusinessContextType(); // construct the facade client return new CustomAppPropertiesFacadeClient(businessContext, callbackHandler); } } a. Provide valid data for constants SITE_ADMIN_USER_ID, SITE_ADMIN_PASSWORD, and STOREENT_ID. b. Save the file (Ctrl+s). 4. Add test cases for creating and deleting CustomAppProperties nouns: a. In CustomAppPropertiesFacadeClientTest.java, add the methods in Example 12-59 that are located between the tearDown() and buildCustomAppPropertiesNoun() methods. Review the processCustomAppProperties() method to see how to: Create a client Construct the Process BOD command request Create an action (create or delete) using an XPath expression Build the CustomAppProperties noun Send the request
Example 12-59 Test methods for creating and deleting CustomAppProperties nouns
/** * This method tests create CustomAppProperties by sending * the ProcessCustomAppProperties BOD with action add. */ public void test_01_ProcessCustomAppPropertiesCreate() { System.out .println("=== START test_01_ProcessCustomAppPropertiesCreate ==="); // define test inputs
382
CustomAppPropertiesData testData = new CustomAppPropertiesData(); testData.setName("aCustomAppProperty"); testData.setStoreent_id(STOREENT_ID); testData.setDescription("The description of aCustomAppProperty"); testData.setCategory("aCategory"); testData.setValue("The value of aCustomAppProperty"); testData.setValueType("SimpleText"); // Create the BOD request for creating CustomAppProperties AcknowledgeCustomAppPropertiesType acknowledgeCAP = createCustomAppProperties(testData); // verify name in response String resultName = ((CustomAppPropertiesType) acknowledgeCAP.getDataArea() .getCustomAppProperties().get(0)).getCustomAppPropertiesIdentifier() .getExternalIdentifier().getName(); assertEquals("Test case failed because actual result '" + resultName + "' is not equals to the expceted result " + testData.getName(), resultName, testData.getName()); // verify response List errorList = acknowledgeCAP.getDataArea().getAcknowledge().getResponseCriteria(); if (errorList != null && errorList.size() != 0) fail("Test Case failed because the response has error"); System.out .println("+++ Test Case test_01_ProcessCustomAppPropertiesCreate passed. +++"); } /** * This method tests delete CustomAppProperties by sending * the ProcessCustomAppProperties BOD with action delete. * The noun is identified by external ID. */ public void test_06_ProcessCustomAppPropertiesDelete_ExID() { System.out .println("=== START test_06_ProcessCustomAppPropertiesDelete_ExID ==="); // define test inputs CustomAppPropertiesData testData = new CustomAppPropertiesData(); testData.setName("aCustomAppProperty"); testData.setStoreent_id(STOREENT_ID);
383
// Create the BOD request for deleting CustomAppProperties AcknowledgeCustomAppPropertiesType acknowledgeCAP = deleteCustomAppProperties(testData); // verify response List errorList = acknowledgeCAP.getDataArea().getAcknowledge().getResponseCriteria(); if (errorList != null && errorList.size() != 0) fail("Test Case failed because the response has error"); System.out .println("+++ Test Case test_06_ProcessCustomAppPropertiesDelete_ExID passed. +++"); } /** * Create a new CustomAppProperties noun * and returns an acknowledge BOD * @param testData the data to execute the test * @return AcknowledgeCustomAppPropertiesType the acknowledge BOD after creating a CustomAppProperties noun */ private AcknowledgeCustomAppPropertiesType createCustomAppProperties( CustomAppPropertiesData testData) { return processCustomAppProperties( testData, CustomAppPropertiesFacadeConstants.PROCESS_VERB_ACTION_CREATE_CUSTOMAPPPROPERTIES); } /** * Deletes a CustomAppProperties noun * and returns an acknowledge BOD * @param testData the data to execute the test * @return AcknowledgeCustomAppPropertiesType the acknowledge BOD after creating a CustomAppProperties noun */ private AcknowledgeCustomAppPropertiesType deleteCustomAppProperties( CustomAppPropertiesData testData) { return processCustomAppProperties( testData, CustomAppPropertiesFacadeConstants.PROCESS_VERB_ACTION_DELETE_CUSTOMAPPPROPERTIES); } /**
384
* Processes a CustomAppProperties noun * And returns an acknowledge BOD * @param testData the data to execute the test * @param argProcessVerb the action to process * @return AcknowledgeCustomAppPropertiesType the acknowledge BOD after creating a CustomAppProperties noun */ private AcknowledgeCustomAppPropertiesType processCustomAppProperties( CustomAppPropertiesData testData, String argProcessVerb) { // Create service facade client // For Process BOD, you need to provide Site Admin credentials CustomAppPropertiesFacadeClient iClient = constructFacadeClient(SITE_ADMIN_USER_ID, SITE_ADMIN_PASSWORD); // Create Process BOD ProcessCustomAppPropertiesType processCAP = CustomAppPropertiesFactory.eINSTANCE .createProcessCustomAppPropertiesType(); processCAP.setDataArea(CustomAppPropertiesFactory.eINSTANCE .createProcessCustomAppPropertiesDataAreaType()); // Create the noun CustomAppPropertiesType capNoun = buildCustomAppPropertiesNoun(testData); // Create the action using a pre-defined XPath expression List actions = new ArrayList(); actions.add(AbstractBusinessObjectDocumentFacadeClient.createActionExpression( argProcessVerb, SelectionCriteriaHelper.STR_XPATH_LANG, CustomAppPropertiesFacadeConstants.XPATH_EMPTY_CUSTOMAPPPROPERTIES)); ProcessType processVerb = AbstractBusinessObjectDocumentFacadeClient .createProcessVerb(actions); processCAP.getDataArea().setProcess(processVerb); // Add noun into the BOD processCAP.getDataArea().getCustomAppProperties().add(capNoun); // logging the request BOD System.out.println(" The request BOD is sent: "); System.out.println(SDOHelper.toString((DataObject) processCAP)); // Send the BOD request AcknowledgeCustomAppPropertiesType acknowledgeCAP = iClient .processCustomAppProperties(processCAP);
385
// logging the response BOD System.out.println(" The response BOD is returned: "); System.out.println(SDOHelper.toString((DataObject) acknowledgeCAP)); return acknowledgeCAP; } 5. Add test cases for updating the CustomAppProperties nouns: a. In CustomAppPropertiesFacadeClientTest.java, add: The method test_04_ChangeCustomAppProperties(), which is provided in Example 12-60 between the test_01_ProcessCustomAppPropertiesCreate() and test_05_ProcessCustomAppPropertiesDelete_ExID() methods. The updateCustomAppProperties() method, which is also provided in Example 12-60 after the processCustomAppProperties() method. Create a client Construct the Change BOD command request Create an action using an XPath expression Build the CustomAppProperties noun Send the request
/** * This method tests update CustomAppProperties by sending * the ChangeCustomAppProperties BOD. * First, the UniqueID will be retrieved. Then the description will be updated. */ public void test_05_ChangeCustomAppProperties() { System.out.println("=== START test_05_ChangeCustomAppProperties ==="); // define input to retrieve UniqueID CustomAppPropertiesData retrieveData = new CustomAppPropertiesData(); retrieveData.setName("aCustomAppProperty"); retrieveData.setStoreent_id(STOREENT_ID); // Create the BOD request for fetching CustomAppProperties ShowCustomAppPropertiesType showCAP = getCustomAppProperties(retrieveData, CustomAppPropertiesFacadeConstants.ACCESS_PROFILE_ALL_INFORMATION, CustomAppPropertiesFacadeConstants.XPATH_CUSTOMAPPPROPERTIES_EXTERNAL_ID); // Read UniqueID from response
386
long uniqueId = ((CustomAppPropertiesType) showCAP.getDataArea() .getCustomAppProperties().get(0)).getCustomAppPropertiesIdentifier() .getUniqueID(); // define test inputs CustomAppPropertiesData testData = new CustomAppPropertiesData(); testData.setUniqueID(uniqueId); testData.setDescription("The UPDATED description of aCustomAppProperty"); // Create the BOD request for creating CustomAppProperties RespondCustomAppPropertiesType respondCAP = updateCustomAppProperties(testData); // verify description has changed in response String resultDesc = ((CustomAppPropertiesType) respondCAP.getDataArea() .getCustomAppProperties().get(0)).getDescription(); assertEquals("Test case failed because actual result '" + resultDesc + "' is not equals to the expceted result " + testData.getDescription(), resultDesc, testData.getDescription()); // verify response List errorList = respondCAP.getDataArea().getRespond().getResponseCriteria(); if (errorList != null && errorList.size() != 0) fail("Test Case failed because the response has error"); System.out .println("+++ Test Case test_05_ChangeCustomAppProperties passed. +++"); } /** * Updates a CustomAppProperties noun * and returns an respond BOD. * Uses Change BOD command. * @param testData the data to execute the test * @return AcknowledgeCustomAppPropertiesType the acknowledge BOD after creating a CustomAppProperties noun */ private RespondCustomAppPropertiesType updateCustomAppProperties( CustomAppPropertiesData testData) { // Create service facade client // For Change BOD, you need to provide Site Admin credentials CustomAppPropertiesFacadeClient iClient = constructFacadeClient(SITE_ADMIN_USER_ID, SITE_ADMIN_PASSWORD); // Create Change BOD
387
ChangeCustomAppPropertiesType changeCAP = CustomAppPropertiesFactory.eINSTANCE .createChangeCustomAppPropertiesType(); changeCAP.setDataArea(CustomAppPropertiesFactory.eINSTANCE .createChangeCustomAppPropertiesDataAreaType()); // Create the noun CustomAppPropertiesType capNoun = buildCustomAppPropertiesNoun(testData); // Create the action using a pre-defined XPath expression List actions = new ArrayList(); actions.add(AbstractBusinessObjectDocumentFacadeClient.createActionExpression( AbstractBusinessObjectDocumentFacadeClient.CHANGE_VERB_ACTION_CHANGE, SelectionCriteriaHelper.STR_XPATH_LANG, CustomAppPropertiesFacadeConstants.XPATH_EMPTY_CUSTOMAPPPROPERTIES)); com.ibm.commerce.oagis9.datatypes.ChangeType changeVerb = AbstractBusinessObjectDocumentFacadeClient .createChangeVerb(actions); changeCAP.getDataArea().setChange(changeVerb); // Add noun into the BOD changeCAP.getDataArea().getCustomAppProperties().add(capNoun); // logging the request BOD System.out.println(" The request BOD is sent: "); System.out.println(SDOHelper.toString((DataObject) changeCAP)); // Send the BOD request RespondCustomAppPropertiesType respondCAP = iClient .changeCustomAppProperties(changeCAP); // logging the response BOD System.out.println(" The response BOD is returned: "); System.out.println(SDOHelper.toString((DataObject) respondCAP)); return respondCAP; } 6. Add test cases for reading CustomAppProperties nouns: a. In CustomAppPropertiesFacadeClientTest.java, add: The methods test_02_GetCustomAppPropertiesByExtId(), test_03_GetCustomAppPropertiesByCatgByStore(), and test_04_GetCustomAppPropertiesBySearchByStore(), as provided in Example 12-61 on page 389 between the following methods:
388
test_01_ProcessCustomAppPropertiesCreate() and test_05_ChangeCustomAppProperties(). Add the getCustomAppProperties() method, which is in Example 12-61 before the buildCustomAppPropertiesNoun() method. Create a client Construct the Get BOD command request using an XPath expression Build the CustomAppProperties noun Send the request
You do not need to define an action because the action of a Get command is always Fetch.
Example 12-61 Test methods for reading CustomAppProperties nouns
/** * This method tests fetching CustomAppProperties by sending * the GetCustomAppProperties BOD command. */ public void test_02_GetCustomAppPropertiesByExtId() { System.out.println("=== START test_02_GetCustomAppPropertiesByExtId ==="); // define test inputs CustomAppPropertiesData testData = new CustomAppPropertiesData(); testData.setName("aCustomAppProperty"); testData.setStoreent_id(STOREENT_ID); // Create the BOD request for fetching CustomAppProperties ShowCustomAppPropertiesType showCAP = getCustomAppProperties(testData, CustomAppPropertiesFacadeConstants.ACCESS_PROFILE_ALL_INFORMATION, CustomAppPropertiesFacadeConstants.XPATH_CUSTOMAPPPROPERTIES_EXTERNAL_ID); // verify response List errorList = showCAP.getDataArea().getShow().getResponseCriteria(); if (errorList != null && errorList.size() != 0) fail("Test Case failed because the response has error"); System.out .println("+++ Test Case test_02_GetCustomAppPropertiesByExtId passed. +++"); } /** * This method tests fetching CustomAppProperties by sending * the GetCustomAppProperties BOD command. */
389
public void test_03_GetCustomAppPropertiesByCatgByStore() { System.out .println("=== START test_03_GetCustomAppPropertiesByCatgByStore ==="); // define test inputs CustomAppPropertiesData testData = new CustomAppPropertiesData(); testData.setCategory("aCategory"); testData.setStoreent_id(STOREENT_ID); // Create the BOD request for fetching CustomAppProperties ShowCustomAppPropertiesType showCAP = getCustomAppProperties( testData, CustomAppPropertiesFacadeConstants.ACCESS_PROFILE_ALL_INFORMATION, CustomAppPropertiesFacadeConstants.XPATH_CUSTOMAPPPROPERTIES_BY_CATEGORY_BY_STOREENT_ ID); // verify response List errorList = showCAP.getDataArea().getShow().getResponseCriteria(); if (errorList != null && errorList.size() != 0) fail("Test Case failed because the response has error"); System.out .println("+++ Test Case test_03_GetCustomAppPropertiesByCatgByStore passed. +++"); } /** * This method tests fetching CustomAppProperties by sending * the GetCustomAppProperties BOD command. */ public void test_04_GetCustomAppPropertiesBySearchByStore() { System.out .println("=== START test_04_GetCustomAppPropertiesBySearchByStore ==="); // define test inputs CustomAppPropertiesData testData = new CustomAppPropertiesData(); testData.setStoreent_id(STOREENT_ID); testData.setSearchExpression("contains(NAME,'tomappprop') and CATEGORY='aCategory'"); // Create the BOD request for fetching CustomAppProperties ShowCustomAppPropertiesType showCAP = getCustomAppProperties( testData, CustomAppPropertiesFacadeConstants.ACCESS_PROFILE_SEARCH,
390
CustomAppPropertiesFacadeConstants.XPATH_PATTERN_CUSTOMAPPPROPERTIES_SEARCH_BY_STOREE NT_ID); // verify response List errorList = showCAP.getDataArea().getShow().getResponseCriteria(); if (errorList != null && errorList.size() != 0) fail("Test Case failed because the response has error"); System.out .println("+++ Test Case test_04_GetCustomAppPropertiesBySearchByStore passed. +++"); } /** * * @param testData the data to execute the test * @param accessProfile the access profile * @param xpath the XPath expression * @return ShowCustomAppPropertiesType The result of the data fetch */ private ShowCustomAppPropertiesType getCustomAppProperties( CustomAppPropertiesData testData, String accessProfile, String xpath) { // Create service facade client // For Get BOD, you need no special credentials CustomAppPropertiesFacadeClient iClient = constructFacadeClient("", ""); // build fully resolved XPath expression String xpathResolved = XpathExpressionHelper.resolveXpathExpression(xpath, testData .getAsMap(), accessProfile); // construct the verb GetCustomAppPropertiesType getCAP = CustomAppPropertiesFactory.eINSTANCE .createGetCustomAppPropertiesType(); getCAP.setDataArea(CustomAppPropertiesFactory.eINSTANCE .createGetCustomAppPropertiesDataAreaType()); getCAP.getDataArea().setGet( AbstractBusinessObjectDocumentFacadeClient.createGetVerb( SelectionCriteriaHelper.STR_XPATH_LANG, xpathResolved)); // logging the request BOD System.out.println(" The request BOD is sent: "); System.out.println(SDOHelper.toString((DataObject) getCAP)); // send the "get" request and get the "show" response
391
ShowCustomAppPropertiesType showCustomAppProperties = iClient .getCustomAppProperties(getCAP); // logging the response BOD System.out.println(" The response BOD is returned: "); System.out.println(SDOHelper.toString((DataObject) showCustomAppProperties)); return showCustomAppProperties; } b. Save the file (Ctrl+s). 7. Organize the imports for the CustomAppProperties-UnitTests project: a. Close all open Java files from the CustomAppProperties-Server project. a. Open the Java perspective in WebSphere Commerce Developer. b. Right-click the CustomAppProperties-UnitTests\src folder, and select Source. c. Select Organize Imports. 8. Ensure that there are no remaining compilation errors.
392
9. Start the WebSphere Commerce Test Server by going to the Server view, right-clicking the WebSphere Commerce Test Server, and selecting Start. 10.If the WebSphere Commerce project is not already published to the WebSphere Commerce Test Server, publish the WebSphere Commerce project: a. From the Server view, right-click the WebSphere Commerce Test Server, and select Add and Remove Projects. b. Select the WC project. c. Click Add. Click Finish. 11.If the WC project is already published, right-click the WebSphere Commerce Test Server, and select Publish. You will perform the unit tests within 12.6.1, Testing the CustomAppProperties service using unit tests on page 411.
393
394
Inside the <_config:ErrorGroup> sub-element, each <_config:ReasonCodeParameterAssociation> sub-element defines a message key for a specific error reason code. In this scenario we do not extend the error messages. 7. Update the wc-customappproperties-clientobjects.xml file: a. Remove all existing <_config:URLParameter> sub-elements. b. For every noun element in the CustomAppProperties noun, add an appropriate <_config:URLParameter> sub-element within the <_config:URLParameterGroup> element: i. Add an attribute key with value true to the noun element of the UniqueID to flag the primary key. ii. Add an attribute return with value true to the noun elements of the UniqueID, Storeent_id, and Name to include their values in the response message to the Management Center client application. c. Ensure that your file now has the same content that is in Example 12-62.
Example 12-62 wc-customappproperties-clientobjects.xml file
<?xml version="1.0" encoding="UTF-8"?> <_config:URLtoOAGIS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.ibm.com/xmlns/prod/commerce/foundation/config ../xsd/url-to-oagis.xsd " xmlns:_config="http://www.ibm.com/xmlns/prod/commerce/foundation/config"> <_config:NounDefinitions> <_config:Noun name="CustomAppProperties" /> </_config:NounDefinitions> <_config:URLDefinitions> <_config:URLParameterGroup name="CustomAppProperties" noun="CustomAppProperties"> <_config:URLParameter name="configId" nounElement="/CustomAppPropertiesIdentifier/UniqueID" key="true" return="true" /> <_config:URLParameter name="key" nounElement="/CustomAppPropertiesIdentifier/ExternalIdentifier/Name" return="true" /> <_config:URLParameter name="storeId" nounElement="/CustomAppPropertiesIdentifier/ExternalIdentifier/Storeent_id" return="true" /> <_config:URLParameter name="value" nounElement="/Value" /> <_config:URLParameter name="type" nounElement="/ValueType" type="ValueTypeEnum" /> <_config:URLParameter name="version" nounElement="/Version" />
395
<_config:URLParameter name="category" nounElement="/Category" /> <_config:URLParameter name="description" nounElement="/Description" /> </_config:URLParameterGroup> </_config:URLDefinitions> <_config:ErrorDefinitions primaryResourceBundle="com.redbooks.commerce.customappproperties.client.lobtools.prop erties.CustomAppPropertiesLOBErrorMessages" alternateResourceBundle="extensions.com.redbooks.commerce.customappproperties.client. lobtools.properties.CustomAppPropertiesLOBErrorMessages"> <_config:ErrorGroup name="CustomAppProperties"> <_config:ReasonCodeParameterAssociation reasonCode="_APP_CUSTOMAPPPROPERTIES_CUSTOMAPPPROPERTIES_NAME_ALREADY_EXISTS" parameterName="name" /> <_config:ReasonCodeParameterAssociation reasonCode="_APP_CUSTOMAPPPROPERTIES_CUSTOMAPPPROPERTIES_NAME_EMPTY" parameterName="name" /> </_config:ErrorGroup> </_config:ErrorDefinitions> </_config:URLtoOAGIS> d. Save the file (Ctrl+S).
396
5. Open the get-data-config.xml file, and review the pre-built file: The <data-type> element defines a Java interface to the logical SDO. This setting is correct; therefore, you do not change it. The <client-facade> element defines a Java interface to the client facade code. This setting is correct; therefore, you do not change it. The <expression-builder> element defines an XPath expression template, along with an access profile and the data type of the returning nouns, which is the CustomAppProperties noun that is defined in the <data-type> element before. The settings do not reflect the actual needs, so you will provide your own expression templates in the next step. 6. Update the get-data-config.xml file: a. Remove the existing <expression-builder> element. b. Add <expression-builder> elements for these expression templates: {_wcf.ap=$accessProfile$}/CustomAppProperties[(Category='$catego ry$') and CustomAppPropertiesIdentifier[ExternalIdentifier[(Storeent_id= $storeent_id$)]]] with access profile Redbooks_All and name findCustomAppPropertiesByCategoryAndStoreent_id {_wcf.ap=$accessProfile$}/CustomAppProperties[CustomAppPropertie sIdentifier[ExternalIdentifier[(Storeent_id=$storeent_id$)]] and search(contains(NAME,'$name$') or contains(VALUE,'$value$') or contains(DESCRIPTION,'$description$'))] with access profile Redbooks_Search and name findCustomAppPropertiesByStoreent_idAndSearchTerm {_wcf.ap=$accessProfile$}/CustomAppProperties/CustomAppPropertie sIdentifier/ExternalIdentifier[(Storeent_id=$storeent_id$)] with access profile Redbooks_Category and name findCustomAppPropertiesCategoriesByStoreent Note: You must not leave any white space characters between <expression-builder> tags and the expression templates. The open tag, expression template, and closing tag must be on one line: <expression-builder>expression_template</expression-builder> Blanks, tabs, new line characters, and so on lead to a non-working configuration.
397
The expression templates must match any of the query template file definitions of the wc-query-CustomAppProperties-get.tpl file. Refer back to Defining the wc-query-CustomAppProperties-get.tpl query template on page 347. The access profiles are specified as <param> sub-elements. They are referenced in the expression template using the notation of $param$. The other parameters are set by URL requests. The parameter names must match the definition from the wc-customappproperties-clientobjects.xml file. The parameter names that are used in the search() expression, such as NAME or VALUE, must match the definition that is stored in the wc-business-object-mediator.xml file. Refer back to Updating the CustomAppProperties service configuration on page 355 for more information. c. Ensure that your file now has the same content that is in Example 12-63.
Example 12-63 get-data-config.xml file
<?xml version="1.0" encoding="UTF-8"?> <_config:get-data-config xmlns:_config="http://www.ibm.com/xmlns/prod/commerce/foundation/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.ibm.com/xmlns/prod/commerce/foundation/config ../../xsd/get-data-config.xsd "> <data-type> <name>CustomAppProperties</name> <type>com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ype</type> </data-type> <client-facade> <data-type-name>CustomAppProperties</data-type-name> <class>com.redbooks.commerce.customappproperties.facade.client.CustomAppPropertiesFac adeClient</class> <method>getCustomAppProperties</method> </client-facade> <expression-builder> <name>findCustomAppPropertiesByCategoryAndStoreent_id</name> <data-type-name>CustomAppProperties</data-type-name> <expression-template>{_wcf.ap=$accessProfile$}/CustomAppProperties[(Category='$catego ry$') and
398
CustomAppPropertiesIdentifier[ExternalIdentifier[(Storeent_id=$storeent_id$)]]]</expr ession-template> <param> <name>accessProfile</name> <value>Redbooks_All</value> </param> </expression-builder> <expression-builder> <name>findCustomAppPropertiesByStoreent_idAndSearchTerm</name> <data-type-name>CustomAppProperties</data-type-name> <expression-template>{_wcf.ap=$accessProfile$}/CustomAppProperties[CustomAppPropertie sIdentifier[ExternalIdentifier[(Storeent_id=$storeent_id$)]] and search(contains(NAME,'$name$') or contains(VALUE,'$value$') or contains(DESCRIPTION,'$description$'))]</expression-template> <param> <name>accessProfile</name> <value>Redbooks_Search</value> </param> </expression-builder> <expression-builder> <name>findCustomAppPropertiesCategoriesByStoreent</name> <data-type-name>CustomAppProperties</data-type-name> <expression-template>{_wcf.ap=$accessProfile$}/CustomAppProperties/CustomAppPropertie sIdentifier/ExternalIdentifier[(Storeent_id=$storeent_id$)]</expression-template> <param> <name>accessProfile</name> <value>Redbooks_Category</value> </param> </expression-builder> </_config:get-data-config> d. Save the file (Ctrl+S).
399
For extensions to the Management Center Web application, you must update the struts-extension.xml file: 1. Open WebSphere Commerce Developer. 2. Open the J2EE perspective. 3. In the Project Explorer view, expand the LOBTools project. 4. Navigate to the WebContent\WEB-INF\ folder. 5. Open the struts-extension.xml file, which also opens the Struts Configuration File editor. 6. In the Struts Configuration File editor, on the editors bottom line, select the Source tab to work with the XML document source. If this scenario is your only customization to the Management Center Web application, this file only contains empty base elements, such as <data-sources> <form-beans> <global-exceptions> <global-forwards> <action-mappings>
7. Update the struts-extension.xml file: a. Within the <action-mappings> element, add <action> elements to map the Management Center client URLs to action classes and response JSPs. Use these actions to invoke Get commands: /GetConfigCategories: A service call to get a list of configuration setting categories of the current store /GetConfigCategoryChildren: A service call to get a list of configuration settings of the queried category and the current store /FindConfigProperties: A service call to get a list of configuration settings of the current store, according to the search term CreateCustomAppProperties: A service call to create a new configuration setting and to store it in the database DeleteCustomAppProperties: A service call to delete an existing configuration setting and to remove it from the database UpdateCustomAppProperties: A service call to update an existing configuration setting and to update it in the database
400
b. Ensure that the <action-mappings> element contains at least the content that is in Example 12-64.
Example 12-64 Struts configuration file, <action-mappings> element
<action-mappings> <action path="/GetConfigCategories" forward="/jsp/redbooks/config/GetConfigCategories.jsp" /> <action path="/GetConfigCategoryChildren" forward="/jsp/redbooks/config/GetConfigCategoryChildren.jsp" /> <action path="/FindConfigProperties" forward="/jsp/redbooks/config/FindConfigProperties.jsp" /> <action path="/CreateCustomAppProperties" parameter="CustomAppProperties" type="com.ibm.commerce.foundation.client.facade.bod.servlet.struts.BusinessObjectDocu mentAction" className="com.ibm.commerce.foundation.client.facade.bod.servlet.struts.BusinessObjec tDocumentActionMapping"> <set-property property="contextParameters" value="storeId" /> <set-property property="verb" value="Process" /> <set-property property="documentRootFactory" value="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppProperties Factory" /> <set-property property="clientLibrary" value="com.redbooks.commerce.customappproperties.facade.client.CustomAppPropertiesFac adeClient" /> <set-property property="clientLibraryMethod" value="processCustomAppProperties" /> <set-property property="actionCode" value="Create" /> <forward name="success" path="/jsp/redbooks/config/RespondProcessCustomAppProperties.jsp" /> </action> <action path="/UpdateCustomAppProperties" parameter="CustomAppProperties" type="com.ibm.commerce.foundation.client.facade.bod.servlet.struts.BusinessObjectDocu mentAction" className="com.ibm.commerce.foundation.client.facade.bod.servlet.struts.BusinessObjec tDocumentActionMapping"> <set-property property="contextParameters" value="storeId" /> <set-property property="verb" value="Change" /> <set-property property="documentRootFactory" value="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppProperties Factory" />
401
<set-property property="clientLibrary" value="com.redbooks.commerce.customappproperties.facade.client.CustomAppPropertiesFac adeClient" /> <set-property property="clientLibraryMethod" value="changeCustomAppProperties" /> <set-property property="actionCode" value="Change" /> <forward name="success" path="/jsp/redbooks/config/RespondChangeCustomAppProperties.jsp" /> </action> <action path="/DeleteCustomAppProperties" parameter="CustomAppProperties" type="com.ibm.commerce.foundation.client.facade.bod.servlet.struts.BusinessObjectDocu mentAction" className="com.ibm.commerce.foundation.client.facade.bod.servlet.struts.BusinessObjec tDocumentActionMapping"> <set-property property="contextParameters" value="storeId" /> <set-property property="verb" value="Process" /> <set-property property="documentRootFactory" value="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppProperties Factory" /> <set-property property="clientLibrary" value="com.redbooks.commerce.customappproperties.facade.client.CustomAppPropertiesFac adeClient" /> <set-property property="clientLibraryMethod" value="processCustomAppProperties" /> <set-property property="actionCode" value="Delete" /> <forward name="success" path="/jsp/redbooks/config/RespondProcessCustomAppProperties.jsp" /> </action> </action-mappings> c. Review Example 12-64 on page 401. The action mappings for Process and Change commands need a number of additional property settings: contextParameters A comma separated list of the URL parameters that represent information that is placed in the business context area of the request. Unless the colon override is specified, the name of the context parameter is the URL parameter, for example, if masterCatalogId:catatlogId is specified, the URL parameter value of masterCatalogId is used, but the context parameter name is catalog. If storeId is specified, the URL parameter and context name is storeId. This setting is optional. In our scenario, we only use storeId without parameter name mapping.
402
documentRootFactory The factory that is used to create the BOD object. In our scenario, we use the generated class com.redbooks.commerce.customappproperties.facade.datatypes.Cust omAppPropertiesFactory from the CustomAppProperties-DataObjects project.
verb The verb of the BOD documentRootFactory. In our scenario, the supported verbs are Process and Change.
clientLibrary The class name of the client library to invoke. In our scenario, we use the generated class com.redbooks.commerce.customappproperties.facade.client.CustomA ppPropertiesFacadeClient from the CustomAppProperties-Client project.
clientLibraryMethod The method to invoke on the client library to initiate the service request. In our scenario, we use the generated methods changeCustomAppProperties() and processCustomAppProperties().
actionCode The actionCode of the request. If this is not specified, the URL request must specify the actionCode as a URL parameter. In our scenario, we use the pre-defined action codes Create, Change, and Delete.
defaultParameters A list of parameters in query string format. This setting is optional. In our scenario, we do not use default parameters.
d. Create a <plug-in> element to announce the client-service-mapping file for the Custom Application Configuration Management tool. In our scenario, we use the file wc-customappproperties-clientobjects.xml in the directory /WEB-INF/config/com.redbooks.commerce.customappproperties. e. Ensure that the <plug-in> element contains the same content that is in Example 12-65 on page 404.
403
404
5. Create folders for the Custom Application Configuration Management tool JSPs: a. b. c. d. e. f. Right-click the jsp folder, and select New Folder. In the Folder name field, type redbooks. Click Finish. Right-click the redbooks folder, and select New Folder. In the Folder name field, type config. Click Finish. Right-click the config folder, and select New Folder. In the Folder name field, type serialize. Click Finish.
6. Create JSP files: a. Right-click the config folder, and select New JSP file. b. In the File Name field, type GetConfigCategories.jsp. Click Finish. c. Repeat steps a and b to create the JSP files GetConfigCategoryChildren.jsp and FindConfigProperties.jsp. d. Right-click the serialize folder, select New Other. e. From the New wizard, select Simple File. Click Next. f. In the File name field, type SerializeCustomAppProperty.jspf. Click Finish. g. Repeat steps d and f to create the serialization JSP fragments: SerializeCustomAppPropCategory.jspf and SerializeCustomAppPropertyForSearch.jspf. 7. Implement the /GetConfigCategories action: a. Open the GetConfigCategories.jsp file. b. Replace the files content completely with the content in Example 12-66. The expressionBuilder attribute of the <wcf:getData> tag refers to the <expression-builder> element in the CustomAppProperties component get-data-config.xml file, using the storeent_id parameter from the request parameter storeId.
Example 12-66 GetConfigCategories.jsp file
<?xml version="1.0" encoding="UTF-8"?> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://commerce.ibm.com/foundation" prefix="wcf"%> <% // Use the get-data tag to invoke the expression which will find // CustomAppProperties by storeId. %> <objects> <wcf:getData
405
type="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ype[]" var="customAppPropCategories" expressionBuilder="findCustomAppPropertiesCategoriesByStoreent" varShowVerb="showVerb" > <wcf:param name="storeent_id" value="${param.storeId}" /> </wcf:getData> <c:set var="prevCategory" value="" /> <c:if test="${!(empty customAppPropCategories)}"> <c:forEach var="customAppPropCategory" items="${customAppPropCategories}"> <% // For each returned customAppProperties, convert it into the XML // representation Management Center expects. This is done // by calling a common serialization JSP fragment that is // used by all URLs that is returning the Management Center // version of the CustomAppProperties noun. %> <% // Because we only want to read the list of distinct categories, // we skip every record that has the same category as the previous ones. // The list of customAppProperties is sorted by XAPPPROPS.CATEGORY for // that reason. %> <c:if test="${customAppPropCategory.category != prevCategory}"> <jsp:directive.include file="serialize/SerializeCustomAppPropCategory.jspf" /> <c:set var="prevCategory" value="${customAppPropCategory.category}"/> </c:if> </c:forEach> </c:if> </objects> c. Open the SerializeCustomAppPropCategory.jspf file. d. Replace the files content completely with the content in Example 12-67.
Example 12-67 SerializeCustomAppPropCategory.jspf file
406
8. Implement the /GetConfigCategoryChildren action: a. Open the GetConfigCategoryChildren.jsp file, and replace the files content completely with the content in Example 12-68.
Example 12-68 GetConfigCategoryChildren.jsp file
<?xml version="1.0" encoding="UTF-8"?> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://commerce.ibm.com/foundation" prefix="wcf"%> <% // Use the get-data tag to invoke the expression which will find // CustomAppProperties by storeId and category. %> <objects> <wcf:getData type="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ype[]" var="customAppProperties" expressionBuilder="findCustomAppPropertiesByCategoryAndStoreent_id" varShowVerb="showVerb" > <wcf:param name="storeent_id" value="${param.storeId}" /> <wcf:param name="category" value="${param.configCategoryName}" /> </wcf:getData> <c:if test="${!(empty customAppProperties)}"> <c:forEach var="customAppProperty" items="${customAppProperties}"> <% // For each returned customAppProperties, convert it into the XML // representation Management Center expects. This is done // by calling a common serialization JSP fragment that is // used by all URLs that is returning the Management Center // version of the CustomAppProperties noun. %> <jsp:directive.include file="serialize/SerializeCustomAppProperty.jspf" /> </c:forEach> </c:if> </objects> b. Open the SerializeCustomAppProperty.jspf file, and replace the files content completely with the content in Example 12-69.
Example 12-69 SerializeCustomAppProperty.jspf file
<object objectType="ChildConfigProperty">
407
<childConfigPropertyId><![CDATA[${customAppProperty.customAppPropertiesIdentifier.uni queID}]]></childConfigPropertyId> <object objectType="ConfigProperty"> <configPropertyId><![CDATA[${customAppProperty.customAppPropertiesIdentifier.uniqueID }]]></configPropertyId> <category><![CDATA[${customAppProperty.category}]]></category> <key><![CDATA[${customAppProperty.customAppPropertiesIdentifier.externalIdentifier.na me}]]></key> <value><![CDATA[${customAppProperty.value}]]></value> <type><![CDATA[${customAppProperty.valueType}]]></type> <description><![CDATA[${customAppProperty.description}]]></description> </object> </object> 9. Implement the /FindConfigProperties action: a. Open the FindConfigProperties.jsp file, and replace the files content completely with the content from Example 12-70.
Example 12-70 FindConfigProperties.jsp file
<?xml version="1.0" encoding="UTF-8"?> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://commerce.ibm.com/foundation" prefix="wcf"%> <% // Use the get-data tag to invoke the expression which will find // CustomAppProperties by storeId and search term. %> <objects> <wcf:getData type="com.redbooks.commerce.customappproperties.facade.datatypes.CustomAppPropertiesT ype[]" var="customAppProperties" expressionBuilder="findCustomAppPropertiesByStoreent_idAndSearchTerm" varShowVerb="showVerb" > <wcf:param name="storeent_id" value="${param.storeId}" /> <wcf:param name="name" value="${param.searchText}" /> <wcf:param name="value" value="${param.searchText}" /> <wcf:param name="description" value="${param.searchText}" /> </wcf:getData> <c:if test="${!(empty customAppProperties)}"> <c:forEach var="customAppProperty" items="${customAppProperties}">
408
<% // For each returned customAppProperties, convert it into the XML // representation Management Center expects. This is done // by calling a serialization JSP fragment. %> <jsp:directive.include file="serialize/SerializeCustomAppPropertyForSearch.jspf" /> </c:forEach> </c:if> </objects> b. Open the SerializeCustomAppPropertyForSearch.jspf file, and replace the files content completely with the content from Example 12-71.
Example 12-71 SerializeCustomAppPropertyForSearch.jspf file
<object objectType="ConfigProperty"> <configPropertyId><![CDATA[${customAppProperty.customAppPropertiesIdentifier.uniqueID }]]></configPropertyId> <category><![CDATA[${customAppProperty.category}]]></category> <key><![CDATA[${customAppProperty.customAppPropertiesIdentifier.externalIdentifier.na me}]]></key> <value><![CDATA[${customAppProperty.value}]]></value> <type><![CDATA[${customAppProperty.valueType}]]></type> <description><![CDATA[${customAppProperty.description}]]></description> </object> c. Save all files (Ctrl+Shift+S).
12.5.5 Implementing object mapping JSPs for Process and Change commands
WebSphere Commerce server responds to a Management Center client application Process request by transforming the nouns into the XML representation that is expected by the Management Center. A JSP file is called on the appropriate Struts action to transform nouns into XML format. WebSphere Commerce uses a common JSP structure to build the XML response. This Response JSP receives the BODs from the WebSphere Commerce server and serializes the noun to the needed XML response. In our scenario, the Management Center client application only expects the UniqueID of the successful processed noun, so the JSPs are very simple.
409
To learn more about Processing Management Center services, visit the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm.co mmerce.management-center_customization.doc/refs/rtfprocessservices.htm Add response JSPs for the actions: /CreateCustomAppProperties, /UpdateCustomAppProperties, and /DeleteCustomAppProperties: 1. Open WebSphere Commerce Developer. 2. Open the J2EE perspective. 3. In the Project Explorer view, expand the LOBTools project. 4. Navigate to the WebContent\jsp\redbooks\config folder. 5. Create the JSP files: a. Right-click the config folder, and select New JSP file. b. In the File Name field, type RespondProcessCustomAppProperties.jsp, and click Finish. c. Repeat steps a and b to create the JSP file, RespondChangeCustomAppProperties.jsp. 6. The /CreateCustomAppProperties and /DeleteCustomAppProperties actions use the same response JSP: a. Open the RespondProcessCustomAppProperties.jsp file, and replace the files content completely with the content from Example 12-72.
Example 12-72 RespondProcessCustomAppProperties.jsp file
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <?xml version="1.0" encoding="UTF-8"?> <object> <configPropertyId> <c:out value="${nouns[0].customAppPropertiesIdentifier.uniqueID}"></c:out> </configPropertyId> </object> 7. The /UpdateCustomAppProperties action uses a different response JSP, but with identical content: a. Open the RespondProcessCustomAppProperties.jsp file, and replace the files content completely with the content from Example 12-72. b. Save all files (Ctrl+Shift+S).
410
411
c. Type the following information: Local monitoring port: 81 Hostname: The host name of the WebSphere Commerce Server where the CustomAppProperties service is running. On WebSphere Commerce Developer, this is typically localhost Port:80 for WebSphere Commerce Developer, 8007 for production d. Click OK. e. Select the created TCP/IP monitor, and click Start. f. Click OK. 2. Run the JUnit test: a. Right-click the CustomAppPropertiesFacadeClientTest.java class, and select Run. b. Select JUnit Test. You should see the request and response XML documents transmitted between the client and server on the TCP/IP monitor. If the validation is successful, you will see a green bar on the JUnit view. From the TCP/IP Monitor, you can review the request and response BOD messages that are sent between the client and server as part of the test. Figure 12-35 on page 413 contains example output.
412
413
4. From the Management Center menu, shown in Figure 12-36, select Application Configuration.
5. If no default store is selected, choose the ConsumerDirect store, shown in Figure 12-37, or any store that you want to work with.
6. In the Explorer view, you see the organizational object, Configuration Categories, shown in Figure 12-38. Because there are no configurations in the database at this point, this folder is empty.
Figure 12-38 Custom Application Configuration Management tool initial Explorer view
7. On the Management Center toolbar, click the Create button to launch the property view, as shown in Figure 12-39 on page 415.
414
8. In the Properties view, enter configuration property information, and click Save, and then click Close. Figure 12-40 shows a sample configuration property.
415
9. Because we created a new category, refresh the Explorer view using the context menu for Configuration Categories folder. Right-click the Configuration Categories folder, and select Reload, as shown in Figure 12-41.
10.The new configuration category is displayed under the Configuration Categories folder. Select the new category, and the navigation list that is in the main work area gets populated, as shown in Figure 12-42.
11.To create a new configuration property of the same category, open the context menu for the category, and select New Configuration Property, as shown in Figure 12-43 on page 417.
416
The displayed Properties view should have the Category value pre-populated, as shown in Figure 12-44.
Figure 12-44 Create the custom application property for an existing category
12.To edit an existing Configuration Property, double-click the property in the list view. You can also use the toolbar buttons or context menu as well. 13.To delete an existing Configuration Property, select the configuration property in the list view, and use the toolbar button or context menu, as shown in Figure 12-45 on page 418.
417
14.To test the Search function, in the search bar, enter a search text, and click the Find icon, which displays the search results in the main work area, as shown in Figure 12-46.
418
Appendix A.
Additional material
In this section, we refer you to additional material that you can download from the Internet.
419
420
There are many ways to bring the changes in, and using CVS is one good way, if you have that system available to you. However, if you do not need to compare and merge the contents, we have a simple method. Create an original backup of each target file first, and then make the redbook version the effective copy: 1. Navigate down to the folder that has an extended file, such as /LOBTools/WebContent/WEB-INF. 2. There is an extended version of the struts-extension.xml file in this folder. Make a backup by renaming the original file with an orig. prefix. Click F2, and enter a new name, such as orig.struts-extension.zml. 3. Copy the redbook version over to be the current version. Select the redbook file (such as redbook.struts-extension.xml), and simultaneously press the Ctrl and C keys, and then simultaneously press the Ctrl and V keys. 4. A popup window for the new name is displayed. Enter the actual name to be used (such as struts-extension.xml) by stripping out the first part of the filename. Click OK. You effectively took a backup of your original files, and copied the redbook version over to be the current one. 5. Rebuild the LOBTools project. This method works with version 3.0.1 of the WebSphere Commerce Feature Pack. If you are using a later version, you must bring in the changes manually, or ideally set up a CVS system with which the synchronization can be done in a well-managed and guided manner. The zip contains two versions of the struts-extension.xml file. The first fully inclusive version requires that you imported the Configuration Manager projects to your workspace. If you have not done that, use the second version.
421
422
Related publications
The publications that we list in this section are considered particularly suitable for a more detailed discussion of the topics that we cover in this book.
IBM Redbooks
For information about ordering these publications, see How to get Redbooks on page 432. Note that some of the documents that we reference here might be available in softcopy only: Planning and Managing the Deployment of WebSphere Commerce, SG24-7588 Deploying and Customizing IBM Sales Center for WebSphere Commerce V6, SG24-7249-00
Online resources
These Web sites are also relevant as further information sources: The WebSphere Commerce Information Center is available at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/index.jsp IBM WebSphere Commerce - Feature Packshome page at: http://www-306.ibm.com/software/genservers/commerceproductline/Descr iption2 WebSphere Commerce Accelerator section in the WebSphere Commerce Information Center: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.user.doc/concepts/ctfmc.htm Getting started with the IBM Sales Center section in the WebSphere Commerce Information Center: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.telesales.tsr.doc/misc/welcome.htm
423
WebSphere Commerce Information Center: Rearranging the layout of the Management Center main page: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfmainpagelayou t.htm WebSphere Commerce Information Center: Rearranging the layout of the Management Center status area: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfstatusarealay out.htm WebSphere Commerce Information Center: Rearranging the layout of the Management Center banner: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfbannerlayout. htm WebSphere Commerce Information Center: Changing the logo image: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfchangelogo.ht m WebSphere Commerce Information Center: Rearranging the layout of the Management Center menu: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfappmenulayout .htm WebSphere Commerce V6 Information Center: Changing the tool icon for the Management Center menu: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfchangeappmenu icon.htm WebSphere Commerce V6 Information Center: Changing access control for a Management Center tool: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfcreatelobrole .htm
424
WebSphere Commerce V6 Information Center: Changing default values for settings in the Preferences dialog: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfchangedefault pref.htm WebSphere Commerce V6 Information Center: Adding new fields in the Catalogs tool: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tutorial/ttfadpcat.htm WebSphere Commerce V6 Information Center: Changing the display name for a Management Center object: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfchangedisplay name.htm WebSphere Commerce V6 Information Center: Adding new search conditions in the advanced search of the Catalogs tool: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tutorial/ttfadsca.htm WebSphere Commerce V6 Information Center: Changing a column name in a list view: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfchangecolumnl ist.htm WebSphere Commerce V6 Information Center: Adding new fields in the Catalogs tool: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tutorial/ttfadpcat.htm OpenLaszlo API defined: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/index.jsp?top ic=/com.ibm.commerce.management-center_customization.doc/refs/rtfmai n.htm Creating a new Management Center widget at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfcreatenewwidg ets.htm
Related publications
425
Using or extending Management Center widget at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfuseexistingwi dgets.htm WebSphere Commerce v6 Information Center: Adding new search conditions in the advanced search of the Catalogs tool: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tutorial/ttfadsca3.htm WebSphere Commerce v6 Information Center: Mapping of validation error reason codes: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfmaperror.htm WebSphere Commerce BOD command framework: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/concepts/csdsoaprogmodel.htm Creating a new WebSphere Commerce BOD service module: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tutorial/tsdsoatutorial.htm Developing the Business Logic layer using the BOD command framework: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tasks/tsdcustomizesoasvc.htm Installation of WebSphere Commerce feature packs, refer to Installing WebSphere Commerce Enhancements at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.admin.doc/tasks/tigfepmaster1.htm Enabling the Management Center: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center.doc/tasks/ttfenablecmc.htm Management Center modeling guidelines: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/concepts/ctfguidelines .htm Management Center file directory structure: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/concepts/ctfdirectorys tructure.htm
426
Management Center file and class naming conventions: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/concepts/ctffilenaming .htm OpenLaszlo documentation: http://www.openlaszlo.org OpenLaszlo profiler which measures the time spent in each function: http://wiki.openlaszlo.org/CallProfiler Management Center search tables at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center.doc/refs/rpmmctables.htm Improving performance of case-insensitive searches in the Management Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center.doc/refs/rtforacleperform.htm Review the section Customized WebSphere Commerce Enterprise Application (EAR) assets at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.doc/concepts/cdpcustom_ear.htm WebSphere Commerce deployment checklist at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.samples.doc/refs/sddeploymentchecklist.htm WebSphere Commerce Information Center for packaging Management Center files for deployment: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/tdecreatewar.htm WebSphere Commerce Information Center in the Rolling back your J2EE asset changes section: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.doc/refs/rdptbear_rollback.htm Deploying a WAR module: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.doc/tasks/tdpdeploying_war_assets_entire.htm Logging and Tracing details: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center.doc/tasks/ttflogtrace.htm
Related publications
427
CVSNT version 2.5.03: http://www.cvsnt.org Merchandising associations and the semantic specifiers, see the Info Center documentation: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.user.doc/concepts/cpnmamass.htm Details of working with the library class files, see this Information Center page: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfaddlibrary.ht m Product Primary Object definition: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.openlazlo.doc/lzx/commerce/catalog/objectDefinitions/Produ ctPrimaryObjectDefinition.lzx/catProductPrimaryObjectDefinition.html Entry point for mapping new actions to the Management Service UI - class: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.openlazlo.doc/lzx/commerce/foundation/restricted/CustomSer vice.lzx/wcfCustomService.html Catalog Entry browse grid definition in the Information Center: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.openlazlo.doc/lzx/commerce/catalog/listViewDefinitions/Cat alogEntryGrid.lzx/catCatalogEntryBrowseGrid.html Transforming the URL request to a BOD service, see the Information Center page: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/refs/rtfprocessservice s.htm CNET API: http://api.cnet.com/ Register for a developer key with CNET.com. Use the following Web site to register with CNET: http://membership.cnet.com/1383-4_1-172.html?path=https%3A%2F%2Ffanyv88.com%3A443%2Fhttp%2Fapi.c net.com%2Fdashboard.html
428
Detailed information about loading catalog data using WebSphere Commerce Accelerator: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.user.doc/tasks/tcacsvld.htm ChangePerson: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/index.jsp?top ic=/com.ibm.commerce.component-services.doc/refs/rmschangeperson.htm Defining custom attributes for member URLs in WebSphere Commerce Information Center: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/index.jsp?top ic=/com.ibm.commerce.developer.doc/tasks/tmsdfatt.htm Demonstration of the CheckTextBody in action: http://ws.cdyne.com/SpellChecker/check.asmx?op=CheckTextBody Changing access control for a Management Center tool: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.management-center_customization.doc/tasks/ttfcustomizeshel l.htm Creating a new WebSphere Commerce BOD service module tutorial: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tutorial/tsdsoatutorial.htm Installed WebSphere Commerce Feature Pack 3.0.1: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.admin.doc/tasks/tigfepmaster.htm Enabled WebSphere Commerce services for Feature Pack 3.0.1: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tasks/twvenableservices.htm Setting up your development environment for creating WebSphere Commerce services: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tasks/twvsetupdevenv.htm Installing the Design Pattern Toolkit and the Component Projects design pattern: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tasks/twvdptk.htm
Related publications
429
Creating a WebSphere Commerce service module of the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tasks/twvcreatecomponent.htm Business Object Document Type (BOD) Version in WebSphere Commerce: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/concepts/csdbodversion.htm Extending a noun, refer to the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tasks/twvextendnoun.htm Tutorial: Adding new properties to a WebSphere Commerce service using the Data Service layer: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tutorial/twvfounduserdata.htm Business object mediators is explained in more detail in the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tasks/tsdexposenewdata.htm Business Logic layer, using the BOD command framework, see WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tasks/tsdcustomizesoasvc.htm Database schema object naming considerations, see WebSphere Commerce Developer at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.database.doc/refs/rdbnamingconventions.htm Query templates in WebSphere Commerce Information Center: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/concepts/csdqtf.htm Detailed information about the wc-business-object-mediator.xml file in the WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tasks/tsdexposenewdata.htm Detailed information about the wc-component.xml file in WebSphere Commerce Information Center at: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tasks/tsdextendconfig.htm
430
Data Service layer query processing: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/concepts/csdqueryexec.htm Extending the configuration of a service module to add name-value parameters: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tasks/tsdextendconfig.htm Business Logic layer design patterns: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/concepts/csddesignpatterns.htm Modifying a Get service to use business logic to insert more data for a specific access profile: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/tasks/tsdworkmoredata.htm Business Object Document Get processing pattern: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/concepts/csdsoaget.htm Registering the new command: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tutorial/twvstep3.htm Access control in the BOD command framework: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.developer.soa.doc/concepts/csdaccesscontrol.htm Debugging access control in WebSphere Commerce: http://www.ibm.com/developerworks/websphere/library/techarticles/080 5_callaghan/0805_callaghan.html?ca=dnb-wce061908 JUnit: http://www.junit.org Deploying the client library: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tasks/twvdeployclientlibrary.htm
Related publications
431
Testing a WebSphere Commerce service: http://publib.boulder.ibm.com/infocenter/wchelp/v6r0m0/topic/com.ibm .commerce.component-services.doc/tasks/twvtestcs.htm IBM Redbooks publication, Planning and Managing the Deployment of WebSphere Commerce, SG24-7588, available: http://www.redbooks.ibm.com/abstracts/sg247588.html?Open
432
Index
A
AbstractCustomAppPropertiesFacadeClient 363 access control assets 312 acpload utility 364 actionCode 403 Active Work folder 7 activity builder 6 Adobe Flash program 12 advanced user interface components 7 advancedSearchContentClass 124 AJAX 4 API 4 application definition file 284 application framework of the Management Center 4 applyData method 221 catProductProperties 160 CDYNE service 184, 237 cfgConfigTopObjectDefinition.lzx Custom Application Configuration Management tool 251 change mediators 311, 318 ChangeCustomAppPropertiesBasePartCmdImpl 360 ChangeCustomAppPropertiesBasePartMediator 318 ChangeCustomAppPropertiesMediator 318 class wcfMainWorkbench 30 client engine 18 client side data model 18 client side mashup 143144, 179 clientLibrary 403 clientLibraryMethod 403 Cloudscape 101 CMCPriceComparisonEnabled 176 CNET API 150 CNET service URL 150 code generator 299 CommerceFoundation model 300 common customization scenarios 29 complex type 286 ComponentProjects design pattern 245 consistent user interface paradigm 7 ConsumerDirect store 115 contentdatapath attribute 152 contextParameters 402 correctionsView 195, 199 createEditor method 202 Custom Application Configuration Management tool 249 ChildConfigProperty 250 ConfigCategory 250 ConfigurationTop 250 Custom Application Configuration Management tool (CACMT) 239 custom grid 150 custom grid widget 147 CustomAppProperties 245 CustomAppProperties noun 287, 298 customAppProperties.appdef file 284
B
baseName attribute 148 build cycle 56, 61 Business Logic layer 17, 23, 43, 46, 312 Business Object Document (BOD) 13, 23 Business Object Document Processing Command 23 Business Object Editor widget 43 Business Object Mediators 24 button widget property editor 211 buttons 4 buyable icons 108
C
campaign activities 6 campaigns 6 cancel button 190 catalog entries 5 catalog management 5 catalog tool 143 catCatalogEntryExtraProperties class 112 catManagePricingInformation 160 catManageProduct 160 catMerchandisingAssociation class 97 catMerchandisingAssociationsAdvancedSearchContent 127
433
CustomAppPropertiesBODVersion element 288 CustomAppProperties-Client 283, 285 CustomAppPropertiesClientApplicationMessageKeys 362 CustomAppPropertiesData 373 CustomAppProperties-DataObjects 283, 285 CustomAppProperties-DataObjects project 287 CustomAppPropertiesExternalIdentifierType type 288 CustomAppPropertiesFacadeClient 363 CustomAppPropertiesFacadeClientTest 371 CustomAppPropertiesFacadeConstants class 348 CustomAppPropertiesIdentifierType type 288 CustomAppPropertiesManagers 365 CustomAppPropertiesMetadata class 313 CustomAppProperties-Server 283, 285 CustomAppPropertiesServicesHTTPInterface 285 CustomAppPropertiesServicesJMSInterface 283, 285 CustomAppPropertiesType type 288 CustomAppPropertiesTypeProtectableProxy 363 CustomAppProperties-UnitTests 283, 285 customer relationship management 9
E
Eclipse 63 Eclipse Process Framework (EPF) 50 Eclipse-enabled template engine 283 EJB 16 EJB classes and interfaces 312 e-mail transport channel 6 enablementValue 271 exported scripting variable 131 expression builder 131 expressionBuilder getData tag 131 extCatalogProductPriceGrabber 156 extCatalogProductPriceGrabberComponent 156 extCatalogProductPricePane 161 extCatalogProductPricePropertyGroup 160 extCatalogResourceBundle 148 extConfigCategoryFilter 265 extConfigCategoryPrimaryObjectDefinition 253 extConfigCategoryPropertyInputText 268 extConfigCategoryPropertyReference 252 extConfigOrganizationalObjectDefinition 251 extConfigPropertyGrid 261 extConfigPropertyList 255 extConfigPropertyPrimaryObjectDefinition 256 extConfigPropertySearchGrid 260 extConfigPropertyValidator 257 extConfigResourceBundle 275 extConfigTopObjectDefinition 251 extConfigurationProperties 256 extension points 4 ExternalIdentifier element 288 extFindConfigurations 256 extManagePriceTab 160 extPriceComparisonPreference 172 extPriceGrabberGrid 152 extPriceGrabberGridcolumn 152 extPriceGrabberGridcolumn class 152 extPropertySpellCheck class 188 extraPropertiesId 110 extSpellCheckButton 188, 199, 211 extSpellCheckButton class 200 extSpellCheckButtonWidget 188 extSpellChecker.lzx 234 extSpellChecker.lzx class 191
D
data mediator type 313 Data Service Facade 24 Data Service Layer (DSL) 23 data store 23 database schema object naming 315 datapath 152 datapath attribute 152 datapointer 150, 154 datapointer registers method 154 dataset class 151 defaultParameters 403 deployment 56 Design Pattern Toolkit (DPTK) 59, 283 destroy 158 developer ID 145 DHTML 12 dictionary feature 235 discount calculation rule 6 discounts 6 displayName 124 documentRootFactory 403 doSpellCheck method 211, 213 DPTK plug-in 283
F
FetchCustomAppPropertiesCmdImpl 359
434
G
Gantt chart 41 genmodel 298 getData tag 131 get-data-config.xml 44 global solution design 52 grid 152
local browser cache 7 logical data model 24, 287, 298 logical SDOs 287 long description field 180 LzDataset 150 LzDelegate 157 LzUnit 58
M
macro design 56 macro design phase 52 MainWorkbench.lzx 30 manage campaigns 6 manage e-mail activities 6 manage e-mail templates 6 manage Web activities 6 Management Center 34 Management Center shell 3031 Management Center tools 30 Management Center user interface 30 Management Center Web application 30 marketing management 5 master catalog 5 maxItems getData tag 131 maxOccurs attribute 294 mediation flow 21 Mediation layer 1718, 22, 43 merchandising associations 6 merchant logo 147 merchant name 147 message key definitions 312 messageView 195 meta data configuration 313 metadata initialization class. 313 micro design 56, 59 minOccurs attribute 294 model property 186 Model-View-Controller architecture 19 moveToNextWord method 233
H
handler class 151 highlightedText 195 highlightWord 221 HTML elements 4 HTTP Post method 58
I
IBM Management Center for WebSphere Commerce 3, 9, 12, 49 IBM Sales Center for WebSphere Commerce 9 IBM WebSphere Commerce 3 Identity Token 21 Init service definition 249 InsertMoreData command 359 in-store customer service tasks 9
J
JACL 72 Java programming language 50 JavaScript 12 JavaScript programming language 50 JSON 145 JSP 22 JSP programming 50 JUnit 58 JUnit-based tests 59 Jython type scripting 72
N L
labelsView 195 language specific exception messages 312 line of business tools 4 listClass 124 listTitile 124 name value pair commands 16 name-value-pair URLs 13 new property editor 202 nonbuyable icons 108
Index
435
O
OAGIS message structure 13 OAGIS services 23 Object definitions 249 offerMessage 153 okCancelView 195 onclick handler 201, 212 ondata event 154 ontext event 153 onvalue events 157 Open Unified Process (OpenUP) 50 OpenLaszlo 12, 17, 58, 179 OpenLaszlo application 19 OpenLaszlo classes basegrid 152 basegridcolumn 152 basescrollbar 152 OpenLaszlo client application 58 OpenLaszlo dataset class 151 OpenLaszlo programming model 50 order capture 9 orders 6
property component 156 Property Editor 187 Property Editor class 199 property editors 185 property panels 185 Protectable Proxy class 363 protectable proxy class 312 proxy server 18
R
Rational ClearCase 80 Read and Change mediators 24 read mediator 315 read mediators 311 ReadCustomAppPropertiesMediator 315 ReadCustomAppPropertiesNounPartMediator 315 recordSetReferenceId getData tag 131 recordSetStartNumber getData tag 131 Redbooks Web site 432 Contact us xv redemption limits 6 release candidate 61 replaceWord 221 Representational State Transfer (REST) style 18, 20 resource bundle class 148 REST interface 183 rich text property editor 180 richinputtext class 221 rightButonsView 195 root node 154
P
page reload 4 partKey 145 persistence technology 24 Physical Data Container 24 physical data model 24 plain text editor 180 predefined promotions types 6 preferenceKey 262 Presentation layer 17, 23, 43, 179 price 147 product management 5 Product Property Panel 199 product property panel 180 production deployment 61 products 6 profiler 59 project delivery methods 51 project lifecycle pattern 51 promotion redemption 6 promotion reward 6 Promotions and Marketing tools 41 promotions management 5 promotions tool 6 property 156
S
sales catalogs 5 searchOptions map 123 searchType 124 selection fields 4 selectNext method 234 server side technology 13 Service Data Objects (SDOs) 243, 246 Service Oriented Integration (SOI) 16 service requests 20 serviceKey 148, 150 service-oriented architecture (SOA) 13, 15 serviceURL 148 Servlet Filter 21
436
session beans 312 setModelObject 157 setProperty method 201 setResultSize 154 setSearchOptions method 123 shipping 6 skip button 190 Software Configuration Management (SCM) 57 SOI model 16 sourceCatalogEntryCode 127 spell check 179 Spell Check Editor 187 status area 150 storefront customization 65 storefront implementation 65 StoreIdentifierType type 291 Struts application 21 Struts based Web Application (module) 20 successMessage 153
getData tag 131 variadic 74 varShowVerb getData tag 131 versionID attribute 289
W
wc-component.xml file 355 wc-componentname-clientobjects.xml 44 wcf getData 44 wcfAdvancedSearchContent 123 wcfBaseComboBox 127 wcfChildListEditor 39, 41 wcfCreateService 60, 257 wcfDeleteService 109, 257 wcfEnablementAndCondition 273 wcfEnablementCondition 110 wcfGanttGrid 41 wcfGanttGrid class 41 wcfGetChildrenService element 252 wcfGridPropertyImage class 111 wcfGridText 262 wcfInputText 127 wcfLogger 77 wcfNavigationListDefinition 255 wcfObjectPathFilter 39 wcfObjectProperties 40 wcfObjectPropertyFilter 39 wcfOjbectGrid 39 wcfPrimaryObjectDefinitions 120 wcfPropertiesComponent 156 wcfPropertyCombobox 270 wcfPropertyEditor 188, 202 wcfPropertyInputMultiLineText 270 wcfPropertyInputText 270 wcfPropertyPane 266 wcfPropertyValue 259 wcfReferenceList 41 wcfResourceBundleKey class 148 wcfRichTextEditor 186 wcfSearchDefinition 41 wcfSearchDefnition 123 wcfSearchService 123 wcfService 41 wcfServiceParams 125 wcfUpdateService 257 wcfValueDefinition class 112
T
text input fields 4 textbox 127 textView 195 The Open Applications Group 13 trace code 200 type getData tag 131
U
UI Conceptual Model 54 UI Design Guidelines 54 UI Protoype 54 UniqueID element 288 unknown word count 228 UpdateCatalogEntryExtraProperties 114 updatePropertyObject 158 updatePropertyObject method 157 updateText method 212 user interaction 4 user interface code 7 UserDataType definition 288
V
ValueType field 294 ValueTypeEnum 292 var
Index
437
wcsObjectGrid class 111 Web 1.0 application 4 Web 2.0 evolution 4 Web activity management 6 Web Services Description Language (WSDL) 246 WebSphere Commerce Accelerator 78 WebSphere Commerce Component Projects pattern 283 WebSphere Commerce Developer 50 WebSphere Commerce OAGIS services 43 WebSphere Commerce Services 50, 59 WebSphere Commerce services 30 WebSphere Commerce Tools framework 45 wordForCorrection_SC 208 wsadmin tool 72 WSDL 246
X
Xappprops 315 XML 12, 50, 145 XML response format 145 XML Schema Definition (XSD) 246 XML schema type 286 XML Schema Validation 286 XPath expression templates 396 XpathExpressionHelper 376 Xpaths 61 xprop_buyable property 110 XSD 246, 299 XSD source code 294
Z
zoomable GANTT charts 7
438
Back cover
BUILDING TECHNICAL INFORMATION BASED ON PRACTICAL EXPERIENCE IBM Redbooks are developed by the IBM International Technical Support Organization. Experts from IBM, Customers and Partners from around the world create timely technical information based on realistic scenarios. Specific recommendations are provided to help you implement IT solutions more effectively in your environment.
SG24-7619-00
ISBN 0738431702