comparison artifacts/src/main/java/org/dive4elements/river/artifacts/D4EArtifact.java @ 5867:59ff03ff48f1

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

http://dive4elements.wald.intevation.org