comparison artifacts/src/main/java/org/dive4elements/river/artifacts/FLYSArtifact.java @ 5838:5aa05a7a34b7

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

http://dive4elements.wald.intevation.org