440 likes | 584 Views
ADF Development Live from the trenches. Aino Andriessen AMIS. Aim. Share our experience with ADF development Better ADF development Prevent problems Better maintainance Better applications. JDeveloper 10.1.3.3 ADF 10.1.3.41.57 JHS 10.1.23.2.51. Software studio Project Organization
E N D
ADF Development Live from the trenches Aino Andriessen AMIS
Aim Share our experience with ADF development • Better ADF development • Prevent problems • Better maintainance • Better applications
JDeveloper 10.1.3.3 ADF 10.1.3.41.57 JHS 10.1.23.2.51
Software studio • Project Organization • Development
Software studio • SCM • Continuous integration • Incident management • OTAP • Quality measurement • Testing • Reporting • Artifact repository
SCM • e.g. Subversion • JDev 10.1.3.3 : 1.4.x; before it's 1.3 • JDeveloper extension(s) • Only 1 scm per JDev installation • Limited functionality, but mostly enough • Necessary for correct refactoring of BC's • ! beware of delete and (re)create in 1 'update-commit cycle' • When updating outside JDev, close JDev or make sure that all changes have been saved • Built-in JDev history • very helpful for re-applying post-generation • 'activate' manually the scm connection to include scm history
Continuous Integration • build • test • deploy • Scripted • maven / ant • Automated • Continuum • Dedicated environment
Unit-testing ↑ code control ↑ code quality ↓development time • TDD improves the testibility of your code • Unit-testing increases your ADF skills • Easy debugging of BC's • Automated execution at continuous integration
BC testing • BC tester • JDeveloper JUnit extensions • URL based application module configuration • 'unit-test framework' • application module configuration • authentication • generic functions import oracle.jbo.client.Configuration; ... public void setUp() { _am = Configuration.createRootApplicationModule ("nl.amis.demo.odtug.model.services.HRService" ,"HRServiceLocalURL"); } public void tearDown () { Configuration.releaseRootApplicationModule(_am, true); }
What to test • Synchronicity of BC with database • Basic test for all VO's • get<AM>().get<VO>().executeQuery() • VO instance in AM • Data • Custom interface methods • Entity validators • Custom methods on ADF objects • Non-ADF java classes • ...
Software studio • Project Organization • Development
Project naming • Use comprehensive names for your project. • The name will normally also be used for the deliverables: • model.jar or viewcontroller.jar is not very informative • but it is good practice to include them in the name
Project merge • It's quite easy to merge BC projects • Copy / paste BC's • Add configurations to bc4j.xcfg • Modify bc4j.xcfg - jbo.project option • recompile
Project organization • Organize your BC's in directories • Separate the usage of ViewObjects (VO) in separate Application Modules (AM) • Bundle read-only reference VO's in a nested AM • Use Custom baseclasses • Extend AM baseclass from Jhs baseclass
Naming conventions • Good names make the project much more comprehensible and maintainable • Stick to your conventions • Apply them asap • or you must perform refactoring later on • Do not use the default sequencenumber suffix that JDeveloper often generates. • e.g. EmployeesVw1 • This will make your life miserable in no time.
Examples • Entities • singular • e.g. Employee • Viewobjects • plural • suffix • e.g. EmployeesVw • read-only reference (dropdown, lov) • e.g. EmployeesListVw • indicate the usage • EmployeesByNameVw • Accessors of associations and viewlinks • use plural for list and singular for 1 object • Attributes • Start with capital • Lkp prefix for reference entity and Lkp<entity> for referenced attributes • Trnsnt prefix or suffix for non-database bound attributes • VO instance names • NO number suffix • comprehensible names, especially within hierarchies • e.g. EmployeesByDepartmentVw or JobsListVw
Refactoring • Don't be afraid of refactoring • try to be strongly typed • use unittests for verification • spend some time getting familiar • Refactor with scm enabled • JDeveloper does a rather good job... but misses a few spots • rename VO does not update the <am>.get<VO>() method • compile error, manual correction of method in Impl • rename an entity may not correctly update the association • manual correction in entity XML file • Rename VO • The 'api' (AM instance) is not refactored. • Iterator bindings remain valid • Attribute bindings in adf faces pages are not updated
JHeadstart refactoring • BC refactoring (AM instance, attribute) only requires a 'refresh' (and regenerate). • Custom code (EL expressions, binding) must be done manually • Rename a group : • Obsolete files : • <oldGroupName>.jspx • <oldGroupName>Table.jspx (in case your group has layout style ‘table-form’.) • <oldGroupName>.pageDef.xml • <oldGroupName>-beans.xml • Obsolete entries : • DataBindings.cpx • web.xml (param-name javax.faces.CONFIG_FILES) • db resourcebundles • Generate and wait with saving to identify the old, not modified, files. • Note that custom templates may contain hard-coded references to the old entries
Software studio • Project Organization • Development
Development skills • Diverse set of skills • Standard Java and Enterprise Java • Database • ADF • JSF pagelifecycle • HTML / Javascript / css • JHeadstart • Application Server Administration • SOA • Architecture / design • Security • Object Orientation • ... • Know your tool / framework! • When using JHeadstart, make sure you know how to make an ADF application
General • Use a 'reference Emp project' for research and trials • JHeadstart solutions • Download SRDemo project • RTFM • Oracle forums • http://radio.weblogs.com/0118231/stories/2004/09/23/notYetDocumentedAdfSampleApplications.html • Do scm project branching to test changes with major impact • http://www.oracle.com/technology/products/jdev/htdocs/partners/addins/exchange/jsf/doc/tagdoc/core/imageIndex.html
Logging • Log4J • and Apache Commons Logging • log4j is included with jhs runtime • Do NOT include in deliverable • log4j.properties file on classpath • source path private static Log sLog = LogFactory.getLog(HRServiceImpl.class);
Debugging • Project properties -> custom run configuration • -Djbo.debugoutput=console (file) • -Djbo.logging.show.function=true • http://www.oracle.com/technology/products/jdev/tips/muench/debugger/index.html • http://www.oracle.com/webapps/online-help/jdeveloper/10.1.3/state/content/navId.4/navSetId._/vtAnchor.editing/vtTopicFile.adfdevguide%7Cweb_testdebug~htm/
Application Module / Service • Create impl class • direct access to VO's • Custom interface methods • parameters must be serializable • return type must be serializable • throw JboException • Exceptions are caught in the binding framework and cannot be catched in managed bean. try { sendEmailBinding.execute(); } catch (JboException e) { // never ever catched } if (ADFJSFUtils.hasErrors(bindings) { // ... }
AM Configuration • Datasource for production • URL for testing • Use exactly the same DB connection • Configure the datasource manually • Do not use the generated ones • Normally do not include them with deployment public static void main(String[] args) { launchTester("nl.amis.demo.odtug.model.services","HRServiceLocalURL"); }
Entity Validation • ! No value change : no attribute validation • but entity validation is performed
Entity validation • Testing can be quite easy with unit-tests /** * Test that the salary must be lower than 10000 */ public void testSalaryRule () { HRServiceImpl service = getHRService(); EmployeeImpl emp = (EmployeeImpl) createEntity ("nl.amis.demo.odtug.model.entities.Employee"); try { emp.setSalary(new oracle.jbo.domain.Number (11111)); fail(); } catch (Exception e) { assertTrue(true); } emp.setSalary(new oracle.jbo.domain.Number (9999)); emp.setSalary(new oracle.jbo.domain.Number (-1)); emp.setSalary(null); }
Views with Instead-of triggers • Refresh after insert / update • DB does not allow returning clause OracleSQLBuilderImpl.doEntityDML(401) BEGIN UPDATE EMPLOYEES Employee SET SALARY=? WHERE EMPLOYEE_ID=? RETURNING PHONE_NUMBER INTO ?; END; public boolean isUseReturningClause() { return false; } OracleSQLBuilderImpl.doEntityDML(401) BEGIN UPDATE EMPLOYEES Employee SET SALARY=? WHERE EMPLOYEE_ID=?; SELECT PHONE_NUMBER INTO ? FROM EMPLOYEES WHERE EMPLOYEE_ID=?; END; Note, that you cannot use this to refresh the DB generated primary key.
Sequence based PK • Sequence based • Override entity create(AttributeList) method : • DbSequence • negative temp id that is NOT submitted to the database • Initially easier to use, but the consequences might be more complex • negative temp id, that is not always updated on details : • override entity postChanges() • refactoring to sequence based requires absence of <VO>Impl • scm issues may arise when delete and create in one updatecycle protected void create(AttributeList attributeList) { super.create(attributeList); SequenceImpl seq = new SequenceImpl("EMP_SEQ", getDBTransaction()); setEmployeeId(seq.getSequenceNumber()); }
Constants and literals • Minimize the use of literals in EL-expressions • Use contants • as managed bean property with 'getter' • IsXXX transient attribute on VO
SetActionListener • The setActionListener tag is a declarative way to allow an action source to set a value before navigation • From and to must be both EL-expressions • even constants, e.g. : from="#{'HelloWorld'}" • oracle.jbo.domain.Number cannot be set as constant • Note execution sequence : • actionListener attribute • setActionListener • action • Can also used to set methodbindings <af:setActionListener from="#{bindings.EmployeesVwEmail.inputValue}" to="#{emailBean.emailTo}"/>
Manipulating the model using binding FacesContext ctx = FacesContext.getCurrentInstance(); Application app = ctx.getApplication(); ValueBinding bind = app.createValueBinding("#{bindings}"); BindingContainer bindingContainer = (BindingContainer) bind.getValue(ctx); OperationBinding sendEmailBinding = bindingContainer.getOperationBinding("sendEmail"); sendEmailBinding.execute(); OperationBinding commitBinding = ADFUtils.getBindingContainer().getOperationBinding("Commit"); commitBinding.execute(); <managed-property> <property-name>bindings</property-name> <value>#{bindings}</value> </managed-property> OperationBinding sendEmailBinding = bindings.getOperationBinding("sendEmail"); sendEmailBinding.execute();
JHeadstart • ADF Faces generation • Different types of pages • I18N • Authorization • Search • Lov • FlexItems • ... • velocity based templating to customize generation
General • Organize pagedefs, resourcebundles, beans, regions • Utils : oracle.jheadstart.controller.jsf.util.JsfUtils • Jhs PhaseListeners in JhsCommon-beans.xml • When using multiple faces-config • !! An extra ADFPhaseListener appears in faces-config.xml after adding custom bindings. Multiple PhaseListeners may lead to unpredictable behaviour.
Postgeneration • Velocity templates • Pagedef generation is not templated • but uses JDev mechanism • Documention of pagedef postgen in a <pafegdef>.postgen.txt file • Disable 'clear pagedef before generation' • Note, disable 'Override' does not add new bindings
Templates • Use scm tag / copy to create a copy template to keep the link with the original template in case of updates • Include custom templates : #parse("cxs/misc/include/cxsNlsEntries.vm") • If necessary, use your own .jtp # Custom template QUICK_SEARCH_TEXT_INPUT=odtug/item/find/googleSearchTextInput.vm # no dropdown needed QUICK_SEARCH_DROP_DOWN_REGION=default/common/empty.vm # removed other entries
Templates and nls • create nls entries : (http://technology.amis.nl/blog/?p=1405 ) • Include custom nls-entries in comment ${JHS.nls("<some text (only used in combination with standard jhs text)>" , "<nls_key>" , "<nls_text>" )} <!-- NLS Entries ${JHS.nls("Global Help button label", "GLOBAL_HELP_LABEL", "Help")} -->
The key to success • Organization • Knowledge • Communication • Fun
aino.andriessen@amis.nl http://www.amis.nl http://technology.amis.nl/blog