Wiki source code of The D2W Rule System

Last modified by Theodore Petrosky on 2013/06/18 23:29

Hide last authors
Pascal Robert 9.1 1 == Related articles ==
2
David Holt 31.1 3 [[doc:documentation.Home.Development Architecture.DirectToWeb Architecture.Direct To Web (D2W and ERD2W).What is Direct to Web?.WebHome]]
David Holt 29.1 4 [[doc:documentation.Home.Development Architecture.DirectToWeb Architecture.Direct To Web (D2W and ERD2W).Changing the Rules with Direct to Web.WebHome]]
Pascal Robert 9.1 5
6 = The D2W Rule System =
7
Theodore Petrosky 28.1 8 by {{mention reference="XWiki.ramsey" style="FULL_NAME" anchor="XWiki-ramsey-Bzxot"/}}
Pascal Robert 9.1 9
Theodore Petrosky 27.1 10 As you probably know, WebObjects is built using the Model-View-Controller (MVC) design pattern. The rule system is in the control layer of a WebObjects D2W app. With it, you can define everything from component bindings to application behavior. To a D2W novice, the rule system can seem really intimidating. It isn't well documented and the location of rule system keys that control application behavior aren't immediately obvious either. Hopefully, by the end of this document, you'll have a better idea of where to look and what to do with the WebObjects rule system.
Pascal Robert 9.1 11
12 == Rule system techniques ==
13
14 === Rule system basics ===
15
Theodore Petrosky 27.1 16 To start, let's look at the structure of a rule. At the most basic level, a rule has three fundamental parts: A priority, a qualifier(also known as the 'left hand side' or LHS), and an assignment(also known as the 'right hand side' or RHS). The idea is that rules are 'fired' when the rule's qualifier matches the current state of the D2WContext. Once a rule is fired, evaluation for that particular key path stops. The priority decides the order in which rule are evaluated, and the Assignment defines the logic used to assign a value to the key path the D2WContext is trying to infer.
Pascal Robert 9.1 17
18 That's quite a mouthful, so let's take a look at a very basic example:
19
David Holt 29.1 20 {{code language="none"}}
21 100: *true* => pageWrapperName = "PageWrapper" [WO:com.webobjects.directtoweb.Assignment]
22 {{/code}}
Pascal Robert 9.1 23
24 The parts of this rule are:
25
David Holt 29.1 26 {{code language="none"}}
27 Priority: qualifier => keyPath = value [WO:Assignment class]
28 {{/code}}
Pascal Robert 9.1 29
Theodore Petrosky 27.1 30 In this case, the qualifier is always true, so long as no other rule priority is greater than 100, the pageWrapperName key in the D2WContext will always equal "PageWrapper". The assignment being used is the simple Assignment class, so the string in 'value' is assigned directly to 'keypath.' And since the D2WContext implements NSKeyValueCodingAdditions, you can reference this keyPath in a d2w component using a wod binding like:
Pascal Robert 9.1 31
32 {{noformat}}
33 PageWrapper : WOSwitchComponent {
34 componentName = d2wContext.pageWrapperName;
35 }
36
37 {{/noformat}}
38
Theodore Petrosky 27.1 39 By using a switch component, the rule system decides what component is used to generate the page wrapper. If you do this in all your d2w page components, you can easily switch out a page wrapper using the rule system. Indeed, this is what IS done in every D2W page component I've seen. By defining component bindings like this, you create a clean separation of application controller logic from the application's view layer. Without the rule system, your logic would end up scattered across the view layer and it would be considerably more difficult to maintain.
Pascal Robert 9.1 40
Theodore Petrosky 27.1 41 And that is really the basic concept behind of the whole rule system. Simple, right?
Pascal Robert 9.1 42
Theodore Petrosky 27.1 43 It's worth noting, this answers a fundamental question about the rule system: Where are the keys that I can use in the rule system located? The keys are located in the D2W components. If you are trying to change the behavior of a D2W page or component via the rule system, there's currently no better way than to simply look at the component source and bindings for clues as to what those keys happen to be. As such, when you start writing your own custom D2W components, you should use the same keys consistently or your rules file will become an unmanageable mess. You can create new keys whenever they are necessary, but it is best to reuse the existing ones when possible.
Pascal Robert 9.1 44
45 === Overriding rules ===
46
Theodore Petrosky 27.1 47 Overriding a rule is a simple task. Continuing with the example above, suppose you want to use a different page wrapper for editing objects than you do throughout the rest of the app. In this case, overriding the rule is quite simple:
Pascal Robert 9.1 48
David Holt 29.1 49 {{code language="none"}}
50 101: task='edit' => pageWrapperName = "EditPageWrapper" [WO:com.webobjects.directtoweb.Assignment]
51 {{/code}}
Pascal Robert 9.1 52
Theodore Petrosky 27.1 53 This rule has a higher priority than the previous rule and the qualifier specifies the 'edit' task specifically. What this means is that when the D2WContext's valueForKey("task").equals("edit") == true, then valueForKey("pageWrapperName").equals("EditPageWrapper") will also be true. Instead of using "PageWrapper" defined by the previous rule, the component used when task='edit' will use the "EditPageWrapper" instead. If the task is not edit, then this rule will not fire and the rule with the next highest priority will be evaluated.
Pascal Robert 9.1 54
55 === Rule chaining ===
56
Theodore Petrosky 27.1 57 Rules can also be chained. In other words, one rule may depend on the outcome of another rule. Consider the following pair of rules:
Pascal Robert 9.1 58
Theodore Petrosky 27.1 59 {{code language="none"}}100: *true* => editPageTemplateName = "ProfessionalEditPage" [WO:com.webobjects.directtoweb.Assignment]{{/code}}
60 {{code language="none"}}100: *true* => pageName = "editPageTemplateName" [WO:er.directtoweb.assignments.ERDKeyValueAssignment]{{/code}}
Pascal Robert 9.1 61
Theodore Petrosky 27.1 62 In this case, the key pageName is assigned the string value "ProfessionalEditPage". Notice the two different assignment classes. If com.webobjects.directtoweb.Assignment had been used on both rules, pageName would equal the string "editPageTemplateName" instead of "ProfessionalEditPage." The custom wonder assignment in this case tells the rule system to assign the value of the key editPageTemplateName to the key pageName. Extending this example, you might have
Pascal Robert 9.1 63
Theodore Petrosky 27.1 64 {{code language="none"}}100: look = 'pro' => editPageTemplateName = "ProfessionalEditPage" [WO:com.webobjects.directtoweb.Assignment]{{/code}}
65 {{code language="none"}}100: look = 'fun' => editPageTemplateName = "FunEditPage" [WO:com.webobjects.directtoweb.Assignment]{{/code}}
66 {{code language="none"}}100: *true* => pageName = "editPageTemplateName" [WO:er.directtoweb.assignments.ERDKeyValueAssignment]{{/code}}
Pascal Robert 9.1 67
Ramsey Gurley 23.1 68 If you repeat these rules for your query, inspect, list, etc etc etc pages, you can now switch the entire layout of your app with a single key named 'look.'
Pascal Robert 9.1 69
70 === Rule caching ===
71
Ramsey Gurley 23.1 72 Suppose you now want override your 'look' key.
Pascal Robert 9.1 73
Theodore Petrosky 27.1 74 {{code language="none"}}100: *true* => look = "fun" [WO:com.webobjects.directtoweb.Assignment]{{/code}}
75 {{code language="none"}}105: session.user.isAdmin => look = "pro" [WO:com.webobjects.directtoweb.Assignment]{{/code}}
Pascal Robert 9.1 76
Theodore Petrosky 27.1 77 For the sake of this discussion, let us assume our D2W app is only using Apple's D2W framework (Despite the fact that we are referencing a custom wonder assignment in our chaining). You first visit your 'edit' page for the entity 'Foo' and get the 'fun' template. Now you log in and return to the 'edit' page for the entity 'Foo' and discover the look is stuck on 'fun'. You cannot get the edit foo page to switch to your 'pro' look! This happens because the rule system caches key values for performance. Once a key is evaluated, the rule system may cache it and reuse that value. To resolve this issue, and others like it, you need to understand rule system caching.
Pascal Robert 9.1 78
Theodore Petrosky 27.1 79 Caching is fairly straightforward, but it differs depending on whether you are using Project Wonder's D2W framework or just Apple's. Apple's D2W framework has a set of global keys called significant keys. The default significant keys are task, entity, propertyKey, and configuration. You can add more using D2W.factory().newSignificantKey() method. In the above example, you would only need to add the 'look' and 'session.user.isAdmin' key paths to your global significant keys for everything to work properly. However, you must be careful since the keys are global. If you have too many significant keys, then you will basically have no caching and your application's performance will suffer. You must also be careful which keys you select. For example, including 'session' as a significant key would practically disable caching.
Pascal Robert 9.1 80
81 == Project Wonder's rule system ==
82
Theodore Petrosky 27.1 83 Project Wonder has an overhauled caching system that works in a more granular way. For the remainder of this article I will be discussing the Wonder rule system unless otherwise noted. Wonder has two basic Assignment subclasses you will want to use when creating your own custom assignments. ERDAssignment and ERDDelayedAssignment. These classes differ in the way they handle caching. Values assigned by an ERDAssignment are cached using dependent keys. Values assigned by ERDDelayedAssignment simply are not cached.
Pascal Robert 9.1 84
85 === ERDAssignment ===
86
Theodore Petrosky 27.1 87 ERDAssignment caches values using the ERDComputingAssignmentInterface. That interface defines one method: dependentKeys(). The dependentKeys method takes one string argument representing a keyPath. The return value is an array of keyPaths that will change the outcome of the Assignment value to keyPath. In this way, the Wonder rule system allows for 'significant' keys to be assigned on a per keyPath basis. So, for example, if you create a custom assignment for the 'look' key and the value needs to be re-evaluated based on the current user, then your dependentKeys() method might look something like:
Pascal Robert 9.1 88
89 {{noformat}}
90 public NSArray dependentKeys(String keyPath) {
91 if("look".equals(keyPath)) {
92 return new NSArray(new String[] {"session.user"});
93 }
94 return NSArray.emptyArray();
95 }
96
97 {{/noformat}}
98
Theodore Petrosky 27.1 99 ERDAssignment also does a bit of magic for you in the fire() method. If you were to simply descend from [[Assignment>>url:http://developer.apple.com/documentation/InternetWeb/Reference/WO542Reference/com/webobjects/directtoweb/Assignment.html||shape="rect"]], you would typically override the fire() method and execute your custom logic there. In many cases, you will want to execute different logic based on the keyPath being assigned. So when a ERDAssignment fires, it instead calls a method named by keyForMethodLookup(). This method simply returns keyPath() unless overriden. So, instead of overriding fire() in a ERDAssignment subclass, you create a method by the same name as your keypath. For example, given the following rule that uses a your custom ERDAssignment:
Pascal Robert 9.1 100
David Holt 29.1 101 {{code language="none"}}
102 100: *true* => myKeyPath = "someValue" [WO:com.whatever.MyGreatERDAssignment]
103 {{/code}}
Pascal Robert 9.1 104
105 Your method that handles the assignment would look like:
106
107 {{noformat}}
108 public Object myKeyPath(D2WContext d2wContext) {
109 // Logic goes here to apply a value to the key named by keyPath()
110 }
111
112 {{/noformat}}
113
Theodore Petrosky 27.1 114 In this way, you can define the logic for individual keys separately. This allows you to use a single assignment to resolve values for multiple keyPaths without resorting to a large if/else block. You can extend this behavior further by overriding the keyForMethodLookup() method. If you override this method and return value() for instance, it would then assign a value to your key with a method by the same name as your value.
Pascal Robert 9.1 115
116 {{noformat}}
117 public Object someValue(D2WContext d2wContext) {
118 // Logic goes here to apply a value to the key named by keyPath()
119 }
120
121 {{/noformat}}
122
Theodore Petrosky 27.1 123 This gives you multiple assignment methods per key if you need them without spreading the logic over different assignment classes. It is a very clever solution.
Pascal Robert 9.1 124
125 === ERDDelayedAssignment ===
126
Theodore Petrosky 27.1 127 ERDDelayedAssignment differs from ERDAssignment in that delayed assignments are not cached. The rhs key is evaluated each time the D2WContext needs it. However, there is a catch. The value of a delayed assignment is NOT reevaluated if it is used on the LHS of a rule. Suppose you have the following rule:
Pascal Robert 9.1 128
David Holt 29.1 129 {{code language="none"}}
130 100: *true* => calendarView = 'session.userPreferences.calendarView' [WO:er.directtoweb.assignments.ERDDelayedKeyValueAssignment]
131 {{/code}}
Pascal Robert 9.1 132
Theodore Petrosky 27.1 133 Now, anytime you call d2wContext.calendarView in your components, you'll get the value contained in session.userPreferences.calendarView. You then try to set a componentName using the 'calendarView' key in rules like this:
Pascal Robert 9.1 134
Theodore Petrosky 27.1 135 {{code language="none"}}100: calendarView = 'DAY' => calendarComponentName = "DayView" [WO:er.directtoweb.assignments.ERDDelayedKeyValueAssignment]{{/code}}
136 {{code language="none"}}100: calendarView = 'MONTH' => calendarComponentName = "MonthView" [WO:er.directtoweb.assignments.ERDDelayedKeyValueAssignment]{{/code}}
137 {{code language="none"}}100: calendarView = 'WEEK' => calendarComponentName = "WeekView" [WO:er.directtoweb.assignments.ERDDelayedKeyValueAssignment]{{/code}}
138 {{code language="none"}}100: calendarView = 'YEAR' => calendarComponentName = "YearView" [WO:er.directtoweb.assignments.ERDDelayedKeyValueAssignment]{{/code}}
Pascal Robert 9.1 139
Theodore Petrosky 27.1 140 If you have not called the key from the d2wContext before this rule fires, it won't matter if session.userPreferences.calendarView has changed, and the last value inferred by the rule system will be used. The key is effectively cached for LHS usage. Fortunately, we can simultaneously solve this problem and reduce the number of rules required using another custom Wonder assignment. ERDDelayedSwitchAssignment gives you the ability to move your qualifier to the RHS:
Pascal Robert 9.1 141
David Holt 29.1 142 {{code language="none"}}
143 100: *true* => calendarComponentName = {"qualifierFormat" = "calendarView = '@@'"; "switch" = {"DAY" = "DayView"; "MONTH" = "MonthView"; "WEEK" = "WeekView"; "YEAR" = "YearView"; }; } [WO:ERDDelayedSwitchAssignment]
144 {{/code}}
Pascal Robert 9.1 145
146 == Tips and tricks ==
147
148 ===== Additional rule model files =====
149
Theodore Petrosky 27.1 150 By default, the rule system checks for two rule files in your projects and frameworks. Those are user.d2wmodel and d2w.d2wmodel. If you are using Project Wonder, you can add more by setting the following property in your properties file:
Pascal Robert 9.1 151
David Holt 29.1 152 {{code language="none"}}
153 er.directtoweb.ERD2WModel.additionalModelNames = ("myOther.d2wmodel","yetAnother.d2wmodel")
154 {{/code}}
Pascal Robert 9.1 155
156 ===== Rule tracing =====
157
Theodore Petrosky 32.1 158 You can turn on rule tracing in the console using {{code language="none"}}-D2WTraceRuleFiringEnabled YES{{/code}} in your run/debug configurations. To get more detailed information, you can use [[Wonder's rule system logging>>doc:documentation.Home.Frameworks.ERDivaLook.How to debug a D2W application.WebHome]] instead.
Pascal Robert 9.1 159
160 == Related Links ==
161
Theodore Petrosky 27.1 162 [[Apple's D2W documentation>>url:http://developer.apple.com/legacy/library/#documentation/WebObjects/Developing_With_D2W/Introduction/Introduction.html#//apple_ref/doc/uid/TP30001015||shape="rect"]]
163 [[rule modeler>>url:http://wocommunity.org/documents/tools||shape="rect"]]
David Avendasora 30.1 164 [[doc:documentation.Home.Development Architecture.DirectToWeb Architecture.Direct To Web (D2W and ERD2W).The D2W Rule System.Configuring Rule Modeler.WebHome]]