comparison flys-artifacts/src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java @ 3468:f37e7e8907cb

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

http://dive4elements.wald.intevation.org