comparison flys-artifacts/src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java @ 3651:06a65baae494

merged flys-artifacts/2.9
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:14:43 +0200
parents afc7bfb4800b
children f858028dde5f
comparison
equal deleted inserted replaced
3549:6a8f83c538e3 3651:06a65baae494
1 package de.intevation.flys.artifacts;
2
3 import de.intevation.artifactdatabase.ArtifactDatabaseImpl;
4 import de.intevation.artifactdatabase.DefaultArtifact;
5 import de.intevation.artifactdatabase.ProtocolUtils;
6 import de.intevation.artifactdatabase.data.DefaultStateData;
7 import de.intevation.artifactdatabase.data.StateData;
8 import de.intevation.artifactdatabase.state.DefaultFacet;
9 import de.intevation.artifactdatabase.state.DefaultOutput;
10 import de.intevation.artifactdatabase.state.Facet;
11 import de.intevation.artifactdatabase.state.Output;
12 import de.intevation.artifactdatabase.state.State;
13 import de.intevation.artifactdatabase.state.StateEngine;
14 import de.intevation.artifactdatabase.transition.TransitionEngine;
15 import de.intevation.artifacts.Artifact;
16 import de.intevation.artifacts.ArtifactDatabase;
17 import de.intevation.artifacts.ArtifactDatabaseException;
18 import de.intevation.artifacts.ArtifactFactory;
19 import de.intevation.artifacts.CallContext;
20 import de.intevation.artifacts.CallMeta;
21 import de.intevation.artifacts.Message;
22 import de.intevation.artifacts.common.ArtifactNamespaceContext;
23 import de.intevation.artifacts.common.utils.XMLUtils;
24 import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
25 import de.intevation.flys.artifacts.cache.CacheFactory;
26 import de.intevation.flys.artifacts.context.FLYSContext;
27 import de.intevation.flys.artifacts.model.CalculationMessage;
28 import de.intevation.flys.artifacts.states.DefaultState;
29 import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
30 import de.intevation.flys.utils.FLYSUtils;
31
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.LinkedList;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Set;
40 import java.util.TreeMap;
41
42 import javax.xml.xpath.XPathConstants;
43
44 import net.sf.ehcache.Cache;
45
46 import org.apache.log4j.Logger;
47 import org.w3c.dom.Document;
48 import org.w3c.dom.Element;
49 import org.w3c.dom.Node;
50 import org.w3c.dom.NodeList;
51
52 /**
53 * The default FLYS artifact with convenience added.
54 * (Subclass to get fully functional artifacts).
55 *
56 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
57 */
58 public abstract class FLYSArtifact extends DefaultArtifact {
59
60 /** The logger that is used in this artifact. */
61 private static Logger log = Logger.getLogger(FLYSArtifact.class);
62
63 public static final String COMPUTING_CACHE = "computed.values";
64
65 /** The XPath that points to the input data elements of the FEED document. */
66 public static final String XPATH_FEED_INPUT =
67 "/art:action/art:data/art:input";
68
69 /** The XPath that points to the name of the target state of ADVANCE. */
70 public static final String XPATH_ADVANCE_TARGET =
71 "/art:action/art:target/@art:name";
72
73 public static final String XPATH_MODEL_ARTIFACT =
74 "/art:action/art:template/@uuid";
75
76 public static final String XPATH_FILTER =
77 "/art:action/art:filter/art:out";
78
79 /** The constant string that shows that an operation was successful. */
80 public static final String OPERATION_SUCCESSFUL = "SUCCESS";
81
82 /** The constant string that shows that an operation failed. */
83 public static final String OPERATION_FAILED = "FAILURE";
84
85 /** The identifier of the current state. */
86 protected String currentStateId;
87
88 /** The identifiers of previous states on a stack. */
89 protected List<String> previousStateIds;
90
91 /** The name of the artifact. */
92 protected String name;
93
94 /** The data that have been inserted into this artifact. */
95 protected Map<String, StateData> data;
96
97 /** Mapping of state names to created facets. */
98 protected Map<String, List<Facet>> facets;
99
100 /**
101 * Used to generates "view" on the facets (hides facets not matching the
102 * filter in output of collection); out -&gt; facets.
103 */
104 protected Map<String, List<Facet>> filterFacets;
105
106
107 /**
108 * The default constructor that creates an empty FLYSArtifact.
109 */
110 public FLYSArtifact() {
111 data = new TreeMap<String, StateData>();
112 previousStateIds = new ArrayList<String>();
113 facets = new HashMap<String, List<Facet>>();
114 }
115
116 /**
117 * This method appends the static data - that has already been inserted by
118 * the user - to the static node of the DESCRIBE document.
119 *
120 * @param doc The document.
121 * @param ui The root node.
122 * @param context The CallContext.
123 * @param uuid The identifier of the artifact.
124 */
125 protected void appendStaticUI(
126 Document doc,
127 Node ui,
128 CallContext context,
129 String uuid)
130 {
131 List<String> stateIds = getPreviousStateIds();
132
133 FLYSContext flysContext = FLYSUtils.getFlysContext(context);
134 StateEngine engine = (StateEngine) flysContext.get(
135 FLYSContext.STATE_ENGINE_KEY);
136
137 boolean debug = log.isDebugEnabled();
138
139 for (String stateId: stateIds) {
140 if (debug) {
141 log.debug("Append static data for state: " + stateId);
142 }
143 DefaultState state = (DefaultState) engine.getState(stateId);
144
145 ui.appendChild(state.describeStatic(this, doc, ui, context, uuid));
146 }
147 }
148
149
150 /**
151 * Returns the name of the concrete artifact.
152 *
153 * @return the name of the concrete artifact.
154 */
155 public String getName() {
156 return name;
157 }
158
159
160 /**
161 * Initialize the artifact and insert new data if <code>data</code> contains
162 * information necessary for this artifact.
163 *
164 * @param identifier The UUID.
165 * @param factory The factory that is used to create this artifact.
166 * @param context The CallContext.
167 * @param data Some optional data.
168 */
169 @Override
170 public void setup(
171 String identifier,
172 ArtifactFactory factory,
173 Object context,
174 CallMeta callMeta,
175 Document data)
176 {
177 boolean debug = log.isDebugEnabled();
178
179 if (debug) {
180 log.debug("Setup this artifact with the uuid: " + identifier);
181 }
182
183 super.setup(identifier, factory, context, callMeta, data);
184
185 FLYSContext flysContext = FLYSUtils.getFlysContext(context);
186
187 List<State> states = getStates(context);
188
189 String name = getName();
190
191 if (debug) {
192 log.debug("Set initial state for artifact '" + name + "'");
193 }
194
195 if (states == null) {
196 log.error("No states found from which an initial "
197 + "state could be picked.");
198 }
199 setCurrentState(states.get(0));
200
201 String model = XMLUtils.xpathString(
202 data,
203 XPATH_MODEL_ARTIFACT,
204 ArtifactNamespaceContext.INSTANCE);
205
206 if (model != null && model.length() > 0) {
207 ArtifactDatabase db = (ArtifactDatabase) flysContext.get(
208 ArtifactDatabaseImpl.GLOBAL_CONTEXT_KEY);
209
210 try {
211 initialize(db.getRawArtifact(model), context, callMeta);
212 }
213 catch (ArtifactDatabaseException adbe) {
214 log.error(adbe, adbe);
215 }
216 }
217
218 filterFacets = buildFilterFacets(data);
219 }
220
221
222 /** Get copy of previous state ids as Strings in list. */
223 protected List<String> clonePreviousStateIds() {
224 return new ArrayList<String>(previousStateIds);
225 }
226
227
228 /**
229 * Copies data item from other artifact to this artifact.
230 *
231 * @param other Artifact from which to get data.
232 * @param name Name of data.
233 */
234 protected void importData(FLYSArtifact other, final String name) {
235 if (other == null) {
236 log.error("No other art. to import data " + name + " from.");
237 return;
238 }
239
240 StateData sd = other.getData(name);
241
242 if (sd == null) {
243 log.warn("Other artifact has no data " + name + ".");
244 return;
245 }
246
247 this.addData(name, sd);
248 }
249
250
251 protected Map<String, StateData> cloneData() {
252 Map<String, StateData> copy = new TreeMap<String, StateData>();
253
254 for (Map.Entry<String, StateData> entry: data.entrySet()) {
255 copy.put(entry.getKey(), entry.getValue().deepCopy());
256 }
257
258 return copy;
259 }
260
261 /**
262 * Return a copy of the facet mapping.
263 * @return Mapping of state-ids to facets.
264 */
265 protected Map<String, List<Facet>> cloneFacets() {
266 Map<String, List<Facet>> copy = new HashMap<String, List<Facet>>();
267
268 for (Map.Entry<String, List<Facet>> entry: facets.entrySet()) {
269 List<Facet> facets = entry.getValue();
270 List<Facet> facetCopies = new ArrayList<Facet>(facets.size());
271 for (Facet facet: facets) {
272 facetCopies.add(facet.deepCopy());
273 }
274 copy.put(entry.getKey(), facetCopies);
275 }
276
277 return copy;
278 }
279
280
281 /**
282 * (called from setup).
283 * @param artifact master-artifact (if any, otherwise initialize is not called).
284 */
285 protected void initialize(
286 Artifact artifact,
287 Object context,
288 CallMeta callMeta)
289 {
290 if (!(artifact instanceof FLYSArtifact)) {
291 return;
292 }
293
294 FLYSArtifact flys = (FLYSArtifact)artifact;
295
296 currentStateId = flys.currentStateId;
297 previousStateIds = flys.clonePreviousStateIds();
298 name = flys.name;
299 data = flys.cloneData();
300 facets = flys.cloneFacets();
301 // Do not clone filter facets!
302
303 ArrayList<String> stateIds = (ArrayList<String>) getPreviousStateIds();
304 ArrayList<String> toInitialize = (ArrayList<String>) stateIds.clone();
305
306 toInitialize.add(getCurrentStateId());
307
308 for (String stateId: toInitialize) {
309 State state = getState(context, stateId);
310
311 if (state != null) {
312 state.initialize(artifact, this, context, callMeta);
313 }
314 }
315 }
316
317
318 /**
319 * Builds filter facets from document.
320 * @see filterFacets
321 */
322 protected Map<String, List<Facet>> buildFilterFacets(Document document) {
323
324 NodeList nodes = (NodeList)XMLUtils.xpath(
325 document,
326 XPATH_FILTER,
327 XPathConstants.NODESET,
328 ArtifactNamespaceContext.INSTANCE);
329
330 if (nodes == null || nodes.getLength() == 0) {
331 return null;
332 }
333
334 Map<String, List<Facet>> result = new HashMap<String, List<Facet>>();
335
336 for (int i = 0, N = nodes.getLength(); i < N; ++i) {
337 Element element = (Element)nodes.item(i);
338 String oName = element.getAttribute("name");
339 if (oName.length() == 0) {
340 continue;
341 }
342
343 List<Facet> facets = new ArrayList<Facet>();
344
345 NodeList facetNodes = element.getElementsByTagNameNS(
346 ArtifactNamespaceContext.NAMESPACE_URI,
347 "facet");
348
349 for (int j = 0, M = facetNodes.getLength(); j < M; ++j) {
350 Element facetElement = (Element)facetNodes.item(j);
351
352 String fName = facetElement.getAttribute("name");
353
354 int index;
355 try {
356 index = Integer.parseInt(facetElement.getAttribute("index"));
357 }
358 catch (NumberFormatException nfe) {
359 log.warn(nfe);
360 index = 0;
361 }
362 facets.add(new DefaultFacet(index, fName, ""));
363 }
364
365 if (!facets.isEmpty()) {
366 result.put(oName, facets);
367 }
368 }
369
370 return result;
371 }
372
373
374 /**
375 * Insert new data included in <code>input</code> into the current state.
376 *
377 * @param target XML document that contains new data.
378 * @param context The CallContext.
379 *
380 * @return a document that contains a SUCCESS or FAILURE message.
381 */
382 @Override
383 public Document feed(Document target, CallContext context) {
384 log.debug("FLYSArtifact.feed()");
385
386 Document doc = XMLUtils.newDocument();
387
388 XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
389 doc,
390 ArtifactNamespaceContext.NAMESPACE_URI,
391 ArtifactNamespaceContext.NAMESPACE_PREFIX);
392
393 Element result = creator.create("result");
394 doc.appendChild(result);
395
396 try {
397 saveData(target, context);
398
399 compute(context, ComputeType.FEED, true);
400
401 return describe(target, context);
402 }
403 catch (IllegalArgumentException iae) {
404 // do not store state if validation fails.
405 context.afterCall(CallContext.NOTHING);
406 creator.addAttr(result, "type", OPERATION_FAILED, true);
407
408 result.setTextContent(iae.getMessage());
409 }
410
411 return doc;
412 }
413
414 /**
415 * This method returns a description of this artifact.
416 *
417 * @param data Some data.
418 * @param context The CallContext.
419 *
420 * @return the description of this artifact.
421 */
422 @Override
423 public Document describe(Document data, CallContext context) {
424
425 if (log.isDebugEnabled()) {
426 log.debug(
427 "Describe: the current state is: " + getCurrentStateId());
428 dumpArtifact();
429 }
430
431 FLYSContext flysContext = FLYSUtils.getFlysContext(context);
432
433 StateEngine stateEngine = (StateEngine) flysContext.get(
434 FLYSContext.STATE_ENGINE_KEY);
435
436 TransitionEngine transitionEngine = (TransitionEngine) flysContext.get(
437 FLYSContext.TRANSITION_ENGINE_KEY);
438
439 List<State> reachable = transitionEngine.getReachableStates(
440 this, getCurrentState(context), stateEngine);
441
442 Document description = XMLUtils.newDocument();
443 XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
444 description,
445 ArtifactNamespaceContext.NAMESPACE_URI,
446 ArtifactNamespaceContext.NAMESPACE_PREFIX);
447
448 Element root = ProtocolUtils.createRootNode(creator);
449 description.appendChild(root);
450
451 State current = getCurrentState(context);
452
453 ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
454 ProtocolUtils.appendState(creator, root, current);
455 ProtocolUtils.appendReachableStates(creator, root, reachable);
456
457 appendBackgroundActivity(creator, root, context);
458
459 Element ui = ProtocolUtils.createArtNode(
460 creator, "ui", null, null);
461
462 Element staticUI = ProtocolUtils.createArtNode(
463 creator, "static", null, null);
464
465 Element outs = ProtocolUtils.createArtNode(
466 creator, "outputmodes", null, null);
467 appendOutputModes(description, outs, context, identifier());
468
469 appendStaticUI(description, staticUI, context, identifier());
470
471 Element name = ProtocolUtils.createArtNode(
472 creator, "name",
473 new String[] { "value" },
474 new String[] { getName() });
475
476 Element dynamic = current.describe(
477 this,
478 description,
479 root,
480 context,
481 identifier());
482
483 if (dynamic != null) {
484 ui.appendChild(dynamic);
485 }
486
487 ui.appendChild(staticUI);
488
489 root.appendChild(name);
490 root.appendChild(ui);
491 root.appendChild(outs);
492
493 return description;
494 }
495
496 /** Override me! */
497
498 protected void appendBackgroundActivity(
499 ElementCreator cr,
500 Element root,
501 CallContext context
502 ) {
503 LinkedList<Message> messages = context.getBackgroundMessages();
504
505 if (messages == null) {
506 return;
507 }
508
509 Element inBackground = cr.create("background-processing");
510 root.appendChild(inBackground);
511
512 cr.addAttr(
513 inBackground,
514 "value",
515 String.valueOf(context.isInBackground()),
516 true);
517
518 CalculationMessage message = (CalculationMessage) messages.getLast();
519 cr.addAttr(
520 inBackground,
521 "steps",
522 String.valueOf(message.getSteps()),
523 true);
524
525 cr.addAttr(
526 inBackground,
527 "currentStep",
528 String.valueOf(message.getCurrentStep()),
529 true);
530
531 inBackground.setTextContent(message.getMessage());
532 }
533
534 /**
535 * Append output mode nodes to a document.
536 */
537 protected void appendOutputModes(
538 Document doc,
539 Element outs,
540 CallContext context,
541 String uuid)
542 {
543 List<Output> generated = getOutputs(context);
544
545 if (log.isDebugEnabled()) {
546 log.debug("This Artifact has " + generated.size() + " Outputs.");
547 }
548
549 ProtocolUtils.appendOutputModes(doc, outs, generated);
550 }
551
552
553 /**
554 * This method handles request for changing the current state of an
555 * artifact. It is possible to step forward or backward.
556 *
557 * @param target The incoming ADVANCE document.
558 * @param context The CallContext.
559 *
560 * @return a document that contains a SUCCESS or FAILURE message.
561 */
562 @Override
563 public Document advance(Document target, CallContext context) {
564
565 boolean debug = log.isDebugEnabled();
566
567 Document doc = XMLUtils.newDocument();
568
569 XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
570 doc,
571 ArtifactNamespaceContext.NAMESPACE_URI,
572 ArtifactNamespaceContext.NAMESPACE_PREFIX);
573
574 Element result = ec.create("result");
575
576 String currentStateId = getCurrentStateId();
577 String targetState = XMLUtils.xpathString(
578 target, XPATH_ADVANCE_TARGET, ArtifactNamespaceContext.INSTANCE);
579
580 if (debug) {
581 log.debug("FLYSArtifact.advance() to '" + targetState + "'");
582 }
583
584 if (!currentStateId.equals(targetState)
585 && isStateReachable(targetState, context))
586 {
587 if (debug) {
588 log.debug("Advance: Step forward");
589 }
590
591 List<String> prev = getPreviousStateIds();
592 prev.add(currentStateId);
593
594 setCurrentStateId(targetState);
595
596 if (debug) {
597 log.debug("Compute data for state: " + targetState);
598 }
599 compute(context, ComputeType.ADVANCE, true);
600
601 return describe(target, context);
602 }
603 else if (isPreviousState(targetState, context)) {
604 if (debug) {
605 log.debug("Advance: Step back to");
606 }
607
608 List<String> prevs = getPreviousStateIds();
609 int targetIdx = prevs.indexOf(targetState);
610 int start = prevs.size() - 1;
611
612 destroyStates(prevs, context);
613
614 for (int i = start; i >= targetIdx; i--) {
615 String prev = prevs.get(i);
616 if (debug) {
617 log.debug("Remove state id '" + prev + "'");
618 }
619
620 prevs.remove(prev);
621 facets.remove(prev);
622 }
623
624 destroyState(getCurrentStateId(), context);
625 setCurrentStateId(targetState);
626
627 return describe(target, context);
628 }
629
630 log.warn("Advance: Cannot advance to '" + targetState + "'");
631 ec.addAttr(result, "type", OPERATION_FAILED, true);
632
633 doc.appendChild(result);
634
635 return doc;
636 }
637
638
639 /**
640 * Returns the identifier of the current state.
641 *
642 * @return the identifier of the current state.
643 */
644 public String getCurrentStateId() {
645 return currentStateId;
646 }
647
648
649 /**
650 * Sets the identifier of the current state.
651 *
652 * @param id the identifier of a state.
653 */
654 protected void setCurrentStateId(String id) {
655 currentStateId = id;
656 }
657
658
659 /**
660 * Set the current state of this artifact. <b>NOTE</b>We don't store the
661 * State object itself - which is not necessary - but its identifier. So
662 * this method will just call the setCurrentStateId() method with the
663 * identifier of <i>state</i>.
664 *
665 * @param state The new current state.
666 */
667 protected void setCurrentState(State state) {
668 setCurrentStateId(state.getID());
669 }
670
671
672 /**
673 * Returns the current state of the artifact.
674 *
675 * @return the current State of the artifact.
676 */
677 public State getCurrentState(Object context) {
678 return getState(context, getCurrentStateId());
679 }
680
681
682 /**
683 * Get list of existant states for this Artifact.
684 * @param context Contex to get StateEngine from.
685 * @return list of states.
686 */
687 protected List<State> getStates(Object context) {
688 FLYSContext flysContext = FLYSUtils.getFlysContext(context);
689 StateEngine engine = (StateEngine) flysContext.get(
690 FLYSContext.STATE_ENGINE_KEY);
691 return engine.getStates(getName());
692 }
693
694
695 /**
696 * Get state with given ID.
697 * @param context Context to get StateEngine from.
698 * @param stateID ID of state to get.
699 * @return state with given ID.
700 */
701 protected State getState(Object context, String stateID) {
702 FLYSContext flysContext = FLYSUtils.getFlysContext(context);
703 StateEngine engine = (StateEngine) flysContext.get(
704 FLYSContext.STATE_ENGINE_KEY);
705 return engine.getState(stateID);
706 }
707
708
709 /**
710 * Returns the vector of previous state identifiers.
711 *
712 * @return the vector of previous state identifiers.
713 */
714 protected List<String> getPreviousStateIds() {
715 return previousStateIds;
716 }
717
718
719 /**
720 * Get all previous and the current state id.
721 * @return #getPreviousStateIds() + #getCurrentStateId()
722 */
723 public List<String> getStateHistoryIds() {
724 ArrayList<String> prevIds = (ArrayList) getPreviousStateIds();
725 ArrayList<String> allIds = (ArrayList) prevIds.clone();
726
727 allIds.add(getCurrentStateId());
728 return allIds;
729 }
730
731
732 /**
733 * Adds a new StateData item to the data pool of this artifact.
734 *
735 * @param name the name of the data object.
736 * @param data the data object itself.
737 */
738 protected void addData(String name, StateData data) {
739 this.data.put(name, data);
740 }
741
742
743 protected StateData removeData(String name) {
744 return this.data.remove(name);
745 }
746
747
748 /**
749 * This method returns a specific StateData object that is stored in the
750 * data pool of this artifact.
751 *
752 * @param name The name of the data object.
753 *
754 * @return the StateData object if existing, otherwise null.
755 */
756 public StateData getData(String name) {
757 return data.get(name);
758 }
759
760
761 /** Return named data item, null if not found. */
762 public String getDataAsString(String name) {
763 StateData data = getData(name);
764 return data != null ? (String) data.getValue() : null;
765 }
766
767
768 /**
769 * This method returns the value of a StateData object stored in the data
770 * pool of this Artifact as Integer.
771 *
772 * @param name The name of the StateData object.
773 *
774 * @return an Integer representing the value of the data object or null if
775 * no object was found for <i>name</i>.
776 *
777 * @throws NumberFormatException if the value of the data object could not
778 * be transformed into an Integer.
779 */
780 public Integer getDataAsInteger(String name)
781 throws NumberFormatException
782 {
783 String value = getDataAsString(name);
784
785 if (value != null && value.length() > 0) {
786 return Integer.parseInt(value);
787 }
788
789 return null;
790 }
791
792
793 /**
794 * This method returns the value of a StateData object stored in the data
795 * pool of this Artifact as Double.
796 *
797 * @param name The name of the StateData object.
798 *
799 * @return an Double representing the value of the data object or null if
800 * no object was found for <i>name</i>.
801 *
802 * @throws NumberFormatException if the value of the data object could not
803 * be transformed into a Double.
804 */
805 public Double getDataAsDouble(String name)
806 throws NumberFormatException
807 {
808 String value = getDataAsString(name);
809
810 if (value != null && value.length() > 0) {
811 return Double.parseDouble(value);
812 }
813
814 return null;
815 }
816
817
818 /**
819 * This method returns the value of a StateData object stored in the data
820 * pool of this Artifact as Long.
821 *
822 * @param name The name of the StateData object.
823 *
824 * @return a Long representing the value of the data object or null if
825 * no object was found for <i>name</i>.
826 *
827 * @throws NumberFormatException if the value of the data object could not
828 * be transformed into a Long.
829 */
830 public Long getDataAsLong(String name)
831 throws NumberFormatException
832 {
833 String value = getDataAsString(name);
834
835 if (value != null && value.length() > 0) {
836 return Long.parseLong(value);
837 }
838
839 return null;
840 }
841
842
843 /**
844 * This method returns the value of a StateData object stored in the data
845 * pool of this Artifact is Boolean using Boolean.valueOf().
846 *
847 * @param name The name of the StateData object.
848 *
849 * @return a Boolean representing the value of the data object or null if no
850 * such object is existing.
851 */
852 public Boolean getDataAsBoolean(String name) {
853 String value = getDataAsString(name);
854
855 if (value == null || value.length() == 0) {
856 return null;
857 }
858
859 return Boolean.valueOf(value);
860 }
861
862
863 /**
864 * Add StateData containing a given string.
865 * @param name Name of the data object.
866 * @param value String to store.
867 */
868 public void addStringData(String name, String value) {
869 addData(name, new DefaultStateData(name, null, null, value));
870 }
871
872
873 public Collection<StateData> getAllData() {
874 return data.values();
875 }
876
877
878 public List<Facet> getFacets() {
879 List<Facet> all = new ArrayList<Facet>();
880
881 for (List<Facet> fs: facets.values()) {
882 all.addAll(fs);
883 }
884
885 return all;
886 }
887
888
889 /**
890 * Get facet as stored internally, with equalling name and index than given
891 * facet.
892 * @param facet that defines index and name of facet searched.
893 * @return facet instance or null if not found.
894 */
895 public Facet getNativeFacet(Facet facet) {
896 String name = facet.getName();
897 int index = facet.getIndex();
898
899 for (List<Facet> fs: facets.values()) {
900 for (Facet f: fs) {
901 if (f.getIndex() == index && f.getName().equals(name)) {
902 return f;
903 }
904 }
905 }
906
907 log.warn("Could not find facet: " + name + " at " + index);
908 return null;
909 }
910
911
912 /**
913 * This method stores the data that is contained in the FEED document.
914 *
915 * @param feed The FEED document.
916 * @param xpath The XPath that points to the data nodes.
917 */
918 public void saveData(Document feed, CallContext context)
919 throws IllegalArgumentException
920 {
921 if (feed == null) {
922 throw new IllegalArgumentException("error_feed_no_data");
923 }
924
925 NodeList nodes = (NodeList) XMLUtils.xpath(
926 feed,
927 XPATH_FEED_INPUT,
928 XPathConstants.NODESET,
929 ArtifactNamespaceContext.INSTANCE);
930
931 if (nodes == null || nodes.getLength() == 0) {
932 throw new IllegalArgumentException("error_feed_no_data");
933 }
934
935 boolean debug = log.isDebugEnabled();
936
937 int count = nodes.getLength();
938
939 if (debug) {
940 log.debug("Try to save " + count + " data items.");
941 }
942
943 String uri = ArtifactNamespaceContext.NAMESPACE_URI;
944
945 DefaultState current = (DefaultState) getCurrentState(context);
946
947 FLYSContext flysContext = FLYSUtils.getFlysContext(context);
948 StateEngine engine = (StateEngine) flysContext.get(
949 FLYSContext.STATE_ENGINE_KEY);
950
951 for (int i = 0; i < count; i++) {
952 Element node = (Element)nodes.item(i);
953
954 String name = node.getAttributeNS(uri, "name");
955 String value = node.getAttributeNS(uri, "value");
956
957 if (name.length() > 0 && value.length() > 0) {
958 if (debug) {
959 log.debug("Save data item for '" + name + "' : " + value);
960 }
961
962 StateData model = engine.getStateData(getName(), name);
963
964 StateData sd = model != null
965 ? model.deepCopy()
966 : new DefaultStateData(name, null, null, value);
967
968 addData(
969 name, current.transform(this, context, sd, name, value));
970 }
971 else if (name.length() > 0 && value.length() == 0) {
972 if (removeData(name) != null && debug) {
973 log.debug("Removed data '" + name + "' successfully.");
974 }
975 }
976 }
977
978 current.validate(this);
979 }
980
981
982 /**
983 * Determines if the state with the identifier <i>stateId</i> is reachable
984 * from the current state. The determination itself takes place in the
985 * TransitionEngine.
986 *
987 * @param stateId The identifier of a state.
988 * @param context The context object.
989 *
990 * @return true, if the state specified by <i>stateId</i> is reacahble,
991 * otherwise false.
992 */
993 protected boolean isStateReachable(String stateId, Object context) {
994
995 if (log.isDebugEnabled()) {
996 log.debug("Determine if the state '" + stateId + "' is reachable.");
997 }
998
999 FLYSContext flysContext = FLYSUtils.getFlysContext(context);
1000
1001 State currentState = getCurrentState(context);
1002 StateEngine sEngine = (StateEngine) flysContext.get(
1003 FLYSContext.STATE_ENGINE_KEY);
1004
1005 TransitionEngine tEngine = (TransitionEngine) flysContext.get(
1006 FLYSContext.TRANSITION_ENGINE_KEY);
1007
1008 return tEngine.isStateReachable(this, stateId, currentState, sEngine);
1009 }
1010
1011
1012 /**
1013 * Determines if the state with the identifier <i>stateId</i> is a previous
1014 * state of the current state.
1015 *
1016 * @param stateId The target state identifier.
1017 * @param context The context object.
1018 */
1019 protected boolean isPreviousState(String stateId, Object context) {
1020 if (log.isDebugEnabled()) {
1021 log.debug("Determine if the state '" + stateId + "' is old.");
1022 }
1023
1024 return getPreviousStateIds().contains(stateId);
1025 }
1026
1027
1028 /**
1029 * Computes the hash code of the entered values.
1030 *
1031 * @return a hash code.
1032 */
1033 @Override
1034 public String hash() {
1035 Set<Map.Entry<String, StateData>> entries = data.entrySet();
1036
1037 long hash = 0L;
1038 int shift = 3;
1039
1040 for (Map.Entry<String, StateData> entry: entries) {
1041 String key = entry.getKey();
1042 Object value = entry.getValue().getValue();
1043
1044 hash ^= ((long)key.hashCode() << shift)
1045 | ((long)value.hashCode() << (shift + 3));
1046 shift += 2;
1047 }
1048
1049 return getCurrentStateId() + hash;
1050 }
1051
1052
1053 /**
1054 * Return List of outputs, where combinations of outputname and filtername
1055 * that match content in filterFacets is left out.
1056 * @return filtered Outputlist.
1057 */
1058 protected List<Output> filterOutputs(List<Output> outs) {
1059 if (filterFacets == null || filterFacets.isEmpty()) {
1060 log.debug("No filter for Outputs.");
1061 return outs;
1062 }
1063
1064 boolean debug = log.isDebugEnabled();
1065
1066 if (debug) {
1067 log.debug(
1068 "Filter Facets with " + filterFacets.size() + " filters.");
1069 }
1070
1071 List<Output> filtered = new ArrayList<Output>();
1072
1073 for (Output out: outs) {
1074 String outName = out.getName();
1075
1076 if (debug) {
1077 log.debug(" filter Facets for Output: " + outName);
1078 }
1079
1080 List<Facet> fFacets = filterFacets.get(outName);
1081 if (fFacets != null) {
1082 if (debug) {
1083 log.debug("" + fFacets.size() + " filters for: " + outName);
1084 for (Facet tmp: fFacets) {
1085 log.debug(" filter = '" + tmp.getName() + "'");
1086 }
1087 }
1088
1089 List<Facet> resultFacets = new ArrayList<Facet>();
1090
1091 for (Facet facet: out.getFacets()) {
1092 for (Facet fFacet: fFacets) {
1093 if (facet.getIndex() == fFacet.getIndex()
1094 && facet.getName().equals(fFacet.getName())) {
1095 resultFacets.add(facet);
1096 break;
1097 }
1098 }
1099 }
1100
1101 if (debug) {
1102 log.debug(
1103 "Facets after filtering = " + resultFacets.size());
1104 }
1105
1106 if (!resultFacets.isEmpty()) {
1107 DefaultOutput nout = new DefaultOutput(
1108 out.getName(),
1109 out.getDescription(),
1110 out.getMimeType(),
1111 resultFacets);
1112 filtered.add(nout);
1113 }
1114 }
1115 }
1116
1117 if (debug) {
1118 log.debug("All Facets after filtering = " + filtered.size());
1119 }
1120
1121 return filtered;
1122 }
1123
1124
1125 /**
1126 * Get all outputs that the Artifact can do in this state (which includes
1127 * all previous states).
1128 *
1129 * @return list of outputs
1130 */
1131 public List<Output> getOutputs(Object context) {
1132 if (log.isDebugEnabled()) {
1133 log.debug("##### Get Outputs for: " + identifier() + " #####");
1134 dumpArtifact();
1135 }
1136
1137 List<String> stateIds = getPreviousStateIds();
1138 List<Output> generated = new ArrayList<Output>();
1139
1140 for (String stateId: stateIds) {
1141 DefaultState state = (DefaultState) getState(context, stateId);
1142 generated.addAll(getOutputForState(state));
1143 }
1144
1145 generated.addAll(getCurrentOutputs(context));
1146
1147 return filterOutputs(generated);
1148 }
1149
1150
1151 /**
1152 * Get output(s) for current state.
1153 * @return list of outputs for current state.
1154 */
1155 public List<Output> getCurrentOutputs(Object context) {
1156 DefaultState cur = (DefaultState) getCurrentState(context);
1157
1158 try {
1159 if (cur.validate(this)) {
1160 return getOutputForState(cur);
1161 }
1162 }
1163 catch (IllegalArgumentException iae) { }
1164
1165 return new ArrayList<Output>();
1166 }
1167
1168
1169 /**
1170 * Get output(s) for a specific state.
1171 * @param state State of interest
1172 * @return list of output(s) for given state.
1173 */
1174 protected List<Output> getOutputForState(DefaultState state) {
1175
1176 if (state == null) {
1177 log.error("state == null: This should not happen!");
1178 return new ArrayList<Output>();
1179 }
1180
1181 boolean debug = log.isDebugEnabled();
1182
1183 if (debug) {
1184 log.debug("Find Outputs for State: " + state.getID());
1185 }
1186
1187 List<Output> list = state.getOutputs();
1188 if (list == null || list.isEmpty()) {
1189 if (debug) {
1190 log.debug("-> No output modes for this state.");
1191 }
1192 return new ArrayList<Output>();
1193 }
1194
1195 String stateId = state.getID();
1196
1197 List<Facet> fs = facets.get(stateId);
1198
1199 if (fs == null || fs.isEmpty()) {
1200 if (debug) {
1201 log.debug("No facets found.");
1202 }
1203 return new ArrayList<Output>();
1204 }
1205
1206 List<Output> gen = generateOutputs(list, fs);
1207
1208 if (debug) {
1209 log.debug("State '" + stateId + "' has " + gen.size() + " outs");
1210 }
1211
1212 return gen;
1213 }
1214
1215
1216 /**
1217 * Generate a list of outputs with facets from fs if type is found in list
1218 * of output.
1219 *
1220 * @param list List of outputs
1221 * @param fs List of facets
1222 */
1223 protected List<Output> generateOutputs(List<Output> list, List<Facet> fs) {
1224 List<Output> generated = new ArrayList<Output>();
1225
1226 boolean debug = log.isDebugEnabled();
1227
1228 for (Output out: list) {
1229 Output o = new DefaultOutput(
1230 out.getName(),
1231 out.getDescription(),
1232 out.getMimeType(),
1233 out.getType());
1234
1235 Set<String> outTypes = new HashSet<String>();
1236
1237 for (Facet f: out.getFacets()) {
1238 if (outTypes.add(f.getName()) && debug) {
1239 log.debug("configured facet " + f);
1240 }
1241 }
1242
1243 boolean facetAdded = false;
1244 for (Facet f: fs) {
1245 String type = f.getName();
1246
1247 if (outTypes.contains(type)) {
1248 if (debug) {
1249 log.debug("Add facet " + f);
1250 }
1251 facetAdded = true;
1252 o.addFacet(f);
1253 }
1254 }
1255
1256 if (facetAdded) {
1257 generated.add(o);
1258 }
1259 }
1260
1261 return generated;
1262 }
1263
1264
1265 /**
1266 * Dispatches the computation request to compute(CallContext context, String
1267 * hash) with the current hash value of the artifact which is provided by
1268 * hash().
1269 *
1270 * @param context The CallContext.
1271 */
1272 public Object compute(
1273 CallContext context,
1274 ComputeType type,
1275 boolean generateFacets
1276 ) {
1277 return compute(context, hash(), type, generateFacets);
1278 }
1279
1280
1281 /**
1282 * Dispatches computation requests to the current state which needs to
1283 * implement a createComputeCallback(String hash, FLYSArtifact artifact)
1284 * method.
1285 *
1286 * @param context The CallContext.
1287 * @param hash The hash value which is used to fetch computed data from
1288 * cache.
1289 *
1290 * @return the computed data.
1291 */
1292 public Object compute(
1293 CallContext context,
1294 String hash,
1295 ComputeType type,
1296 boolean generateFacets
1297 ) {
1298 DefaultState current = (DefaultState) getCurrentState(context);
1299 return compute(context, hash, current, type, generateFacets);
1300 }
1301
1302
1303 /**
1304 * Like compute, but identify State by it id (string).
1305 */
1306 public Object compute(
1307 CallContext context,
1308 String hash,
1309 String stateID,
1310 ComputeType type,
1311 boolean generateFacets
1312 ) {
1313 DefaultState current =
1314 (stateID == null)
1315 ? (DefaultState)getCurrentState(context)
1316 : (DefaultState)getState(context, stateID);
1317
1318 if (hash == null) {
1319 hash = hash();
1320 }
1321
1322 return compute(context, hash, current, type, generateFacets);
1323 }
1324
1325
1326 /**
1327 * Let current state compute and register facets.
1328 *
1329 * @param key key of state
1330 * @param state state
1331 * @param type Type of compute
1332 * @param generateFacets Whether new facets shall be generated.
1333 */
1334 public Object compute(
1335 CallContext context,
1336 String key,
1337 DefaultState state,
1338 ComputeType type,
1339 boolean generateFacets
1340 ) {
1341 String stateID = state.getID();
1342
1343 List<Facet> fs = (generateFacets) ? new ArrayList<Facet>() : null;
1344
1345 try {
1346 Cache cache = CacheFactory.getCache(COMPUTING_CACHE);
1347
1348 Object old = null;
1349
1350 if (cache != null) {
1351 net.sf.ehcache.Element element = cache.get(key);
1352 if (element != null) {
1353 log.debug("Got computation result from cache.");
1354 old = element.getValue();
1355 }
1356 }
1357 else {
1358 log.debug("cache not configured.");
1359 }
1360
1361 Object res;
1362 switch (type) {
1363 case FEED:
1364 res = state.computeFeed(this, key, context, fs, old);
1365 break;
1366 case ADVANCE:
1367 res = state.computeAdvance(this, key, context, fs, old);
1368 break;
1369 case INIT:
1370 res = state.computeInit(this, key, context, context.getMeta(), fs);
1371 default:
1372 res = null;
1373 }
1374
1375 if (cache != null && old != res && res != null) {
1376 log.debug("Store computation result to cache.");
1377 net.sf.ehcache.Element element =
1378 new net.sf.ehcache.Element(key, res);
1379 cache.put(element);
1380 }
1381
1382 return res;
1383 }
1384 finally {
1385 if (generateFacets) {
1386 if (fs.isEmpty()) {
1387 facets.remove(stateID);
1388 }
1389 else {
1390 facets.put(stateID, fs);
1391 }
1392 }
1393 }
1394 }
1395
1396
1397 /**
1398 * Method to dump the artifacts state/data.
1399 */
1400 protected void dumpArtifact() {
1401 log.debug("++++++++++++++ DUMP ARTIFACT DATA +++++++++++++++++");
1402 // Include uuid, type, name
1403
1404 log.debug("------ DUMP DATA ------");
1405 Collection<StateData> allData = data.values();
1406
1407 for (StateData d: allData) {
1408 String name = d.getName();
1409 String value = (String) d.getValue();
1410
1411 log.debug("- " + name + ": " + value);
1412 }
1413
1414 log.debug("------ DUMP PREVIOUS STATES ------");
1415 List<String> stateIds = getPreviousStateIds();
1416
1417 for (String id: stateIds) {
1418 log.debug("- State: " + id);
1419 }
1420
1421 log.debug("CURRENT STATE: " + getCurrentStateId());
1422
1423 debugFacets();
1424 dumpFilterFacets();
1425
1426 log.debug("++++++++++++++ END ARTIFACT DUMP +++++++++++++++++");
1427 }
1428
1429
1430 protected void debugFacets() {
1431 log.debug("######### FACETS #########");
1432 Set<Map.Entry<String, List<Facet>>> entries = facets.entrySet();
1433
1434 for (Map.Entry<String, List<Facet>> entry: entries) {
1435 String out = entry.getKey();
1436 List<Facet> fs = entry.getValue();
1437 for (Facet f: fs) {
1438 log.debug(" # " + out + " : " + f.getName());
1439 }
1440 }
1441
1442 log.debug("######## FACETS END ########");
1443 }
1444
1445
1446 protected void dumpFilterFacets() {
1447 log.debug("######## FILTER FACETS ########");
1448
1449 if (filterFacets == null || filterFacets.isEmpty()) {
1450 log.debug("No Filter Facets defined.");
1451 return;
1452 }
1453
1454 Set<Map.Entry<String, List<Facet>>> entries = filterFacets.entrySet();
1455 for (Map.Entry<String, List<Facet>> entry: entries) {
1456 String out = entry.getKey();
1457 List<Facet> filters = entry.getValue();
1458
1459 log.debug("There are " + filters.size() + " filters for: " +out);
1460
1461 for (Facet filter: filters) {
1462 log.debug(" filter: " + filter.getName());
1463 }
1464 }
1465
1466 log.debug("######## FILTER FACETS END ########");
1467 }
1468
1469
1470 protected void destroyState(String id, Object context) {
1471 State s = getState(context, id);
1472 s.endOfLife(this, context);
1473 }
1474
1475
1476 /**
1477 * Calls endOfLife() for each state in the list <i>ids</i>.
1478 *
1479 * @param ids The State IDs that should be destroyed.
1480 * @param context The FLYSContext.
1481 */
1482 protected void destroyStates(List<String> ids, Object context) {
1483 for (int i = 0, num = ids.size(); i < num; i++) {
1484 destroyState(ids.get(i), context);
1485 }
1486 }
1487
1488
1489 /**
1490 * Destroy the states.
1491 */
1492 @Override
1493 public void endOfLife(Object context) {
1494 if (log.isDebugEnabled()) {
1495 log.debug("FLYSArtifact.endOfLife: " + identifier());
1496 }
1497
1498 ArrayList<String> ids = (ArrayList<String>) getPreviousStateIds();
1499 ArrayList<String> toDestroy = (ArrayList<String>) ids.clone();
1500
1501 toDestroy.add(getCurrentStateId());
1502
1503 destroyStates(toDestroy, context);
1504 }
1505 }
1506 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org