1 package com.sri.emo.wizard.completion;
2
3 import com.jcorporate.expresso.core.controller.Input;
4 import com.jcorporate.expresso.core.db.DBException;
5 import com.jcorporate.expresso.core.db.exception.DBRecordNotFoundException;
6 import com.sri.emo.dbobj.Attribute;
7 import com.sri.emo.dbobj.IPartHandler;
8 import com.sri.emo.dbobj.Node;
9 import com.sri.emo.dbobj.Part;
10 import com.sri.emo.wizard.WizardException;
11 import com.sri.emo.wizard.WizardMonitor;
12 import com.sri.emo.wizard.WizardPage;
13 import com.sri.emo.wizard.completion.model.CompletionBean;
14 import com.sri.emo.wizard.completion.model.CompletionPartsBean;
15 import com.sri.emo.wizard.completion.model.FieldCompletion;
16 import com.sri.emo.wizard.defaults.SequentialWizard;
17
18 import java.io.Serializable;
19 import java.util.*;
20
21 /***
22 * A completion wizard.
23 * For the completion wizard pages, ids are java.lang.Integer types with values
24 * <= 0 are special pages. Any values >0 represent a corresponding Part.
25 * Developers should use isPartsPage() for interpretation of this, however.
26 *
27 * @author Michael Rimov
28 */
29 public class EmoCompletionWizard extends SequentialWizard {
30
31 /***
32 *
33 */
34 private static final long serialVersionUID = 1L;
35
36 /***
37 * Constant for the title page id.
38 */
39 public static final String TITLE_PAGE_ID = "-2";
40
41 /***
42 * Constant for the last page (where 'finish' appears) id.
43 */
44 public static final String FINAL_PAGE_ID = "-1";
45
46
47 /***
48 * Underlying completion data bean. This object is not serialized due
49 * to space considerations since it is stateless for the given wizard.
50 * However, for saving state and reconsistuting it via the <tt>Memento</tt>
51 * pattern, then it is set/reset during that phase.
52 */
53 private transient CompletionBean completionBean;
54
55
56 /***
57 * Constructs an emo completion wizard.
58 *
59 * @param wizMonitor WizardMonitor
60 * @param steps WizardPage[]
61 */
62 public EmoCompletionWizard(final WizardMonitor wizMonitor, final WizardPage[] steps) {
63 super(wizMonitor, steps);
64 }
65
66
67 /***
68 * Override of Sequential's onNextPage to allow for validation of values
69 * as they're entered.
70 *
71 * @param previousPage WizardPage
72 * @param nextPage The next page that will be invoked. A 'lookahead' so
73 * to speak.
74 * @param previousPageData Serializable
75 * @return boolean true if the wizard can proceed, false if the previous
76 * page needs to be displayed with validation errors.
77 * @throws WizardException upon error.
78 * @throws AssertionError if the previouspage id is the final page.
79 */
80 protected boolean onNextPage(final WizardPage previousPage, final WizardPage nextPage,
81 final Serializable previousPageData) throws WizardException, AssertionError {
82
83 Integer previousPageId = (Integer) previousPage.getId();
84 assert previousPageId != null;
85
86
87 if (isPartsPage(previousPageId)) {
88
89 return validatePartPageData(previousPage, previousPageData);
90
91 } else if (TITLE_PAGE_ID.equals(previousPageId.toString())) {
92
93 return true;
94
95
96 } else if (FINAL_PAGE_ID.equals(previousPageId.toString())) {
97 assert false:"Final page should not have gotten 'onNext' event";
98 throw new WizardException("Internal Error: received 'next' where the previous page "
99 + "was the final page. Wizard is incorrectly constructed.");
100 } else {
101
102
103
104 if (nextPage.getMetadata() instanceof MultiEntryMetadata) {
105 boolean returnValue = validatePreMultiAttributesPage(previousPage, nextPage, previousPageData);
106 returnValue &= super.onNextPage(previousPage, nextPage, previousPageData);
107 return returnValue;
108
109
110
111 }
112
113
114
115
116 }
117
118
119
120
121 return super.onNextPage(previousPage, nextPage, previousPageData);
122 }
123
124
125 private boolean validatePreMultiAttributesPage(final WizardPage previousPage, final WizardPage nextPage,
126 final Serializable previousPageData) {
127
128
129
130
131
132 MultiEntryWizardPage multiNextPage = (MultiEntryWizardPage) nextPage;
133 MultiEntryMetadata nextPageMetadata = (MultiEntryMetadata) nextPage.getMetadata();
134
135 int numEntries = 0;
136 try {
137
138 if (previousPageData == null || ((String) previousPageData).trim().length() == 0) {
139 numEntries = 0;
140 } else {
141 numEntries = Integer.parseInt((String) previousPageData);
142 }
143 } catch (NumberFormatException ex) {
144 previousPage.addError("You must enter a valid number for this page");
145 return false;
146 }
147
148 boolean hadError = false;
149
150
151
152
153
154
155
156
157 if (nextPageMetadata.getMaxEntries() != null
158 && numEntries > nextPageMetadata.getMaxEntries().intValue()) {
159 previousPage.addError(
160 "Too many. You must enter a value less than or equal to the maximum number of entries: "
161 + nextPageMetadata.getMaxEntries());
162 hadError = true;
163 }
164
165 if (hadError) {
166 return false;
167 }
168
169 multiNextPage.setNumEntries(numEntries);
170 return true;
171 }
172
173 /***
174 * Constant for the minimum possible part number. Anything else is a
175 * 'special page' of some sort.
176 */
177 private static final int MIN_PARTS_NUMBER = 0;
178
179 /***
180 * Overridden target node id. This must be explicitly set by the
181 * wizard controller. Otherwise we use the completion bean.
182 */
183 private String targetNodeId;
184
185 /***
186 * Checks if the given page Id indicates it is a
187 * 'Parts' page.
188 *
189 * @param pageId Integer
190 * @return boolean
191 */
192 protected boolean isPartsPage(final Integer pageId) {
193 assert pageId != null;
194
195 return pageId.intValue() >= MIN_PARTS_NUMBER;
196 }
197
198 /***
199 * @param src WizardPage
200 * @param enteredData Serializable
201 * @return boolean true if the data was validated.
202 * @throws WizardException upon error.
203 */
204 private boolean validatePartPageData(final WizardPage src, final Serializable enteredData) throws WizardException {
205 int partId = ((Integer) src.getId()).intValue();
206
207 try {
208 Part onePart = new Part();
209 onePart.setPartId(partId);
210 onePart.retrieve();
211
212
213 if (onePart.isSingleValued() && enteredData == null) {
214 src.addError("Please enter a value for this page.");
215 return false;
216 }
217 } catch (DBRecordNotFoundException ex) {
218 throw new WizardException("Could not find part of id: " + partId
219 + " perhaps it was deleted by someone else?", ex);
220 } catch (Throwable ex) {
221 throw new WizardException("Error processing page for part id: " + partId, ex);
222 }
223
224 return true;
225 }
226
227
228 /***
229 * This version returns a <tt>Node<tt> instance if one has
230 * been successfully created.
231 * <p>{@inheritDoc}</p>
232 *
233 * @param src WizardPage the source of the event.
234 * @param data This class expects a string for the data.
235 * @param additonalParams anything that the underlying wizard needs. The
236 * values are set by the Application Controller.
237 * @return An instance of a {@link com.sri.emo.dbobj.Node} object
238 * that represents the node the Decision Matrix returned or null if
239 * there was no equivilant data found.
240 * @throws WizardException upon error.
241 */
242 public Object processFinish(final WizardPage src, final Serializable data,
243 final Map additonalParams) throws WizardException {
244 super.processFinish(src, data, additonalParams);
245
246 assert src != null;
247
248 Node node = null;
249
250
251
252 if (this.isDynamicTarget()) {
253 try {
254 node = new Node();
255 node.setNodeId(getTargetNodeId());
256 node.retrieve();
257
258 copyDataIntoTargetNode(node);
259
260 } catch (DBRecordNotFoundException ex) {
261 String message = "Could not find node of id: " + getTargetNodeId();
262 src.addError(message + " with error: " + ex.getMessage());
263 throw new WizardException(message, ex);
264 } catch (Exception e) {
265 src.addError(e.getMessage());
266
267 if (e instanceof RuntimeException) {
268 throw (RuntimeException) e;
269 } else {
270 throw new WizardException(e);
271 }
272 }
273 } else {
274 src.addError("Cannot save to source of wizard design; expected a different (cloned?) destination");
275 }
276
277 return node;
278 }
279
280
281 /***
282 * Returns true if the dynamic target has been specified.
283 *
284 * @return boolean
285 */
286 protected boolean isDynamicTarget() {
287 if (this.getCompletionBean().getTargetId().intValue() == Integer.parseInt(this.getTargetNodeId())) {
288 return false;
289 } else {
290 return true;
291 }
292 }
293
294 /***
295 * Copies the wizard data into the given node.
296 *
297 * @param target Node The node that is set to receive all the data.
298 * @throws Exception upon consturction error, and database errors.
299 */
300 private void copyDataIntoTargetNode(final Node target) throws Exception {
301 CompletionBean completionBean = this.getCompletionBean();
302
303 for (Iterator partsIterator = completionBean.getCompletionParts().iterator(); partsIterator.hasNext();) {
304 CompletionPartsBean onePartBean = (CompletionPartsBean) partsIterator.next();
305
306 if (onePartBean.getFieldCompletion() == FieldCompletion.FIXED) {
307 continue;
308 }
309
310 Part currentPart = onePartBean.getPart();
311
312
313 Object wizardData = this.getAllData().get(new Integer(currentPart.getId()));
314
315 if (currentPart.isHaveCustomHandler()) {
316
317 CustomPartHandlerMetadata meta = (CustomPartHandlerMetadata) getCurrentPage().getMetadata();
318 IPartHandler handler = meta.getCustomHandler();
319 List inputs = meta.getInputList();
320
321 for (Iterator iterator = inputs.iterator(); iterator.hasNext();) {
322 Input input = (Input) iterator.next();
323 handler.saveInput(input);
324 }
325
326 } else if (currentPart.isOwnedAttribute()) {
327
328 if (currentPart.isMultipleAllowed()) {
329 List valueList = (List) wizardData;
330
331 if (valueList == null) {
332 valueList = new ArrayList(0);
333 }
334
335 Attribute[] attributes = target.getAttributes(currentPart.getPartType());
336
337
338 for (int attributeIndex = 0; attributeIndex < attributes.length; attributeIndex++) {
339 attributes[attributeIndex].delete(true);
340 }
341
342 for (Iterator pageValuesIterator = valueList.iterator(); pageValuesIterator.hasNext();) {
343 String onePageValue = (String) pageValuesIterator.next();
344
345 target.addAttribute(currentPart.getPartType(), onePageValue, "");
346 }
347
348 } else {
349 Attribute[] attributes = target.getAttributes(currentPart.getPartType());
350 assert attributes.length == 0 || attributes.length == 1:
351 "Expected only zero or one attributes returned for single attribute";
352
353 if (attributes.length == 0) {
354 if (wizardData != null) {
355 target.addAttribute(currentPart.getPartType(), wizardData.toString(), "");
356 }
357 } else {
358 if (wizardData == null) {
359 target.removeAttribute(attributes[0].getAttribId());
360 } else {
361 target.updateAttribute(attributes[0].getAttribId(), wizardData.toString(), "");
362 }
363 }
364 }
365
366 } else if (currentPart.isSharedNodeAttrib()) {
367
368 assert wizardData instanceof Set;
369
370 Set allRelationsParameterMap = (Set) wizardData;
371 if (allRelationsParameterMap == null) {
372 allRelationsParameterMap = new HashSet(0);
373 }
374 String[] allRelationIds = (String[]) allRelationsParameterMap.toArray(
375 new String[allRelationsParameterMap.size()]);
376
377 target.updateNodeRelations(currentPart.getPartType(), currentPart.getNodeRelation(), allRelationIds);
378 }
379
380 }
381 }
382
383
384 public String getTargetNodeId() {
385 assert completionBean != null;
386
387 if (targetNodeId == null) {
388 return this.completionBean.getTargetId().toString();
389 } else {
390 return targetNodeId;
391 }
392 }
393
394 public Node getTargetNode() throws WizardException {
395 try {
396 Node n = new Node();
397 n.setNodeId(this.getTargetNodeId());
398 n.retrieve();
399 return n;
400 } catch (DBRecordNotFoundException ex) {
401 throw new WizardException("Could no longer find template of id: " + getTargetNodeId()
402 + " perhaps someone else deleted it?");
403 } catch (DBException ex) {
404 throw new WizardException("Error querying database", ex);
405 }
406 }
407
408 /***
409 * Sets the underlying completion bean.
410 *
411 * @param completionBean CompletionBean
412 */
413 public void setCompletionBean(final CompletionBean completionBean) {
414 this.completionBean = completionBean;
415 }
416
417 public void setTargetNodeId(final String targetNodeId) {
418 this.targetNodeId = targetNodeId;
419 }
420
421 public CompletionBean getCompletionBean() {
422 return completionBean;
423 }
424
425
426 }