comparison flys-artifacts/src/main/java/de/intevation/flys/artifacts/FLYSArtifact.java @ 3318:dbe2f85bf160

merged flys-artifacts/2.8
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:14:35 +0200
parents 1b41dc00b1f7
children 0d63581c5df1
comparison
equal deleted inserted replaced
2987:98c7a46ec5ae 3318:dbe2f85bf160
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 logger.debug("Filter Facets with " + filterFacets.size() + " filters.");
1034
1035 List<Output> filtered = new ArrayList<Output>();
1036
1037 for (Output out: outs) {
1038 String outName = out.getName();
1039
1040 logger.debug(" filter Facets for Output: " + outName);
1041
1042 List<Facet> fFacets = filterFacets.get(outName);
1043 if (fFacets != null) {
1044 logger.debug("" + fFacets.size() + " filters for: " + outName);
1045
1046 if (logger.isDebugEnabled()) {
1047 for (Facet tmp: fFacets) {
1048 logger.debug(" filter = '" + tmp.getName() + "'");
1049 }
1050 }
1051
1052 List<Facet> resultFacets = new ArrayList<Facet>();
1053
1054 for (Facet facet: out.getFacets()) {
1055 for (Facet fFacet: fFacets) {
1056 if (facet.getIndex() == fFacet.getIndex()
1057 && facet.getName().equals(fFacet.getName())) {
1058 resultFacets.add(facet);
1059 break;
1060 }
1061 }
1062 }
1063
1064 logger.debug("Facets after filtering = " + resultFacets.size());
1065
1066 if (!resultFacets.isEmpty()) {
1067 DefaultOutput nout = new DefaultOutput(
1068 out.getName(),
1069 out.getDescription(),
1070 out.getMimeType(),
1071 resultFacets);
1072 filtered.add(nout);
1073 }
1074 }
1075 }
1076
1077 logger.debug("All Facets after filtering = " + filtered.size());
1078
1079 return filtered;
1080 }
1081
1082
1083 /**
1084 * Get all outputs that the Artifact can do in this state (which includes
1085 * all previous states).
1086 *
1087 * @return list of outputs
1088 */
1089 public List<Output> getOutputs(Object context) {
1090 logger.debug("##### Get Outputs for: " + identifier() + " #####");
1091
1092 dumpArtifact();
1093
1094 List<String> stateIds = getPreviousStateIds();
1095 List<Output> generated = new ArrayList<Output>();
1096
1097 for (String stateId: stateIds) {
1098 DefaultState state = (DefaultState) getState(context, stateId);
1099 generated.addAll(getOutputForState(state));
1100 }
1101
1102 generated.addAll(getCurrentOutputs(context));
1103
1104 return filterOutputs(generated);
1105 }
1106
1107
1108 /**
1109 * Get output(s) for current state.
1110 * @return list of outputs for current state.
1111 */
1112 public List<Output> getCurrentOutputs(Object context) {
1113 DefaultState cur = (DefaultState) getCurrentState(context);
1114
1115 try {
1116 if (cur.validate(this)) {
1117 return getOutputForState(cur);
1118 }
1119 }
1120 catch (IllegalArgumentException iae) { }
1121
1122 return new ArrayList<Output>();
1123 }
1124
1125
1126 /**
1127 * Get output(s) for a specific state.
1128 * @param state State of interest
1129 * @return list of output(s) for given state.
1130 */
1131 protected List<Output> getOutputForState(DefaultState state) {
1132 logger.debug("Find Outputs for State: " + state.getID());
1133
1134 List<Output> list = state.getOutputs();
1135 if (list == null || list.size() == 0) {
1136 logger.debug("-> No output modes for this state.");
1137 return new ArrayList<Output>();
1138 }
1139
1140 String stateId = state.getID();
1141
1142 List<Facet> fs = facets.get(stateId);
1143
1144 if (fs == null || fs.size() == 0) {
1145 logger.debug("No facets found.");
1146 return new ArrayList<Output>();
1147 }
1148
1149 List<Output> gen = generateOutputs(list, fs);
1150
1151 logger.debug("State '" + stateId + "' has " + gen.size() + " outs");
1152
1153 return gen;
1154 }
1155
1156
1157 /**
1158 * Generate a list of outputs with facets from fs if type is found in list
1159 * of output.
1160 *
1161 * @param list List of outputs
1162 * @param fs List of facets
1163 */
1164 protected List<Output> generateOutputs(List<Output> list, List<Facet> fs) {
1165 List<Output> generated = new ArrayList<Output>();
1166
1167 boolean debug = logger.isDebugEnabled();
1168
1169 for (Output out: list) {
1170 Output o = new DefaultOutput(
1171 out.getName(),
1172 out.getDescription(),
1173 out.getMimeType(),
1174 out.getType());
1175
1176 Set<String> outTypes = new HashSet<String>();
1177
1178 for (Facet f: out.getFacets()) {
1179 if (outTypes.add(f.getName()) && debug) {
1180 logger.debug("configured facet " + f);
1181 }
1182 }
1183
1184 boolean facetAdded = false;
1185 for (Facet f: fs) {
1186 String type = f.getName();
1187
1188 if (outTypes.contains(type)) {
1189 if (debug) {
1190 logger.debug("Add facet " + f);
1191 }
1192 facetAdded = true;
1193 o.addFacet(f);
1194 }
1195 }
1196
1197 if (facetAdded) {
1198 generated.add(o);
1199 }
1200 }
1201
1202 return generated;
1203 }
1204
1205
1206 /**
1207 * Dispatches the computation request to compute(CallContext context, String
1208 * hash) with the current hash value of the artifact which is provided by
1209 * hash().
1210 *
1211 * @param context The CallContext.
1212 */
1213 public Object compute(
1214 CallContext context,
1215 ComputeType type,
1216 boolean generateFacets
1217 ) {
1218 return compute(context, hash(), type, generateFacets);
1219 }
1220
1221
1222 /**
1223 * Dispatches computation requests to the current state which needs to
1224 * implement a createComputeCallback(String hash, FLYSArtifact artifact)
1225 * method.
1226 *
1227 * @param context The CallContext.
1228 * @param hash The hash value which is used to fetch computed data from
1229 * cache.
1230 *
1231 * @return the computed data.
1232 */
1233 public Object compute(
1234 CallContext context,
1235 String hash,
1236 ComputeType type,
1237 boolean generateFacets
1238 ) {
1239 DefaultState current = (DefaultState) getCurrentState(context);
1240 return compute(context, hash, current, type, generateFacets);
1241 }
1242
1243
1244 /**
1245 * Like compute, but identify State by it id (string).
1246 */
1247 public Object compute(
1248 CallContext context,
1249 String hash,
1250 String stateID,
1251 ComputeType type,
1252 boolean generateFacets
1253 ) {
1254 DefaultState current =
1255 (stateID == null)
1256 ? (DefaultState)getCurrentState(context)
1257 : (DefaultState)getState(context, stateID);
1258
1259 if (hash == null) {
1260 hash = hash();
1261 }
1262
1263 return compute(context, hash, current, type, generateFacets);
1264 }
1265
1266
1267 /**
1268 * Let current state compute and register facets.
1269 *
1270 * @param key key of state
1271 * @param state state
1272 * @param type Type of compute
1273 * @param generateFacets Whether new facets shall be generated.
1274 */
1275 public Object compute(
1276 CallContext context,
1277 String key,
1278 DefaultState state,
1279 ComputeType type,
1280 boolean generateFacets
1281 ) {
1282 String stateID = state.getID();
1283
1284 List<Facet> fs = (generateFacets) ? new ArrayList<Facet>() : null;
1285
1286 try {
1287 Cache cache = CacheFactory.getCache(COMPUTING_CACHE);
1288
1289 Object old = null;
1290
1291 if (cache != null) {
1292 net.sf.ehcache.Element element = cache.get(key);
1293 if (element != null) {
1294 logger.debug("Got computation result from cache.");
1295 old = element.getValue();
1296 }
1297 }
1298 else {
1299 logger.debug("cache not configured.");
1300 }
1301
1302 Object res;
1303 switch (type) {
1304 case FEED:
1305 res = state.computeFeed(this, key, context, fs, old);
1306 break;
1307 case ADVANCE:
1308 res = state.computeAdvance(this, key, context, fs, old);
1309 break;
1310 case INIT:
1311 res = state.computeInit(this, key, context, context.getMeta(), fs);
1312 default:
1313 res = null;
1314 }
1315
1316 if (cache != null && old != res && res != null) {
1317 logger.debug("Store computation result to cache.");
1318 net.sf.ehcache.Element element =
1319 new net.sf.ehcache.Element(key, res);
1320 cache.put(element);
1321 }
1322
1323 return res;
1324 }
1325 finally {
1326 if (generateFacets) {
1327 if (fs.isEmpty()) {
1328 facets.remove(stateID);
1329 }
1330 else {
1331 facets.put(stateID, fs);
1332 }
1333 }
1334 }
1335 }
1336
1337
1338 /**
1339 * Method to dump the artifacts state/data.
1340 */
1341 protected void dumpArtifact() {
1342 if (logger.isDebugEnabled()) {
1343 logger.debug("++++++++++++++ DUMP ARTIFACT DATA +++++++++++++++++");
1344 // Include uuid, type, name
1345
1346 logger.debug("------ DUMP DATA ------");
1347 Collection<StateData> allData = data.values();
1348
1349 for (StateData d: allData) {
1350 String name = d.getName();
1351 String value = (String) d.getValue();
1352
1353 logger.debug("- " + name + ": " + value);
1354 }
1355
1356 logger.debug("------ DUMP PREVIOUS STATES ------");
1357 List<String> stateIds = getPreviousStateIds();
1358
1359 for (String id: stateIds) {
1360 logger.debug("- State: " + id);
1361 }
1362
1363 logger.debug("CURRENT STATE: " + getCurrentStateId());
1364
1365 debugFacets();
1366 dumpFilterFacets();
1367
1368 logger.debug("++++++++++++++ END ARTIFACT DUMP +++++++++++++++++");
1369 }
1370 }
1371
1372
1373 protected void debugFacets() {
1374 logger.debug("######### FACETS #########");
1375 Set<Map.Entry<String, List<Facet>>> entries = facets.entrySet();
1376
1377 for (Map.Entry<String, List<Facet>> entry: entries) {
1378 String out = entry.getKey();
1379 List<Facet> fs = entry.getValue();
1380 for (Facet f: fs) {
1381 logger.debug(" # " + out + " : " + f.getName());
1382 }
1383 }
1384
1385 logger.debug("######## FACETS END ########");
1386 }
1387
1388
1389 protected void dumpFilterFacets() {
1390 logger.debug("######## FILTER FACETS ########");
1391
1392 if (filterFacets == null || filterFacets.isEmpty()) {
1393 logger.debug("No Filter Facets defined.");
1394 return;
1395 }
1396
1397 Set<Map.Entry<String, List<Facet>>> entries = filterFacets.entrySet();
1398 for (Map.Entry<String, List<Facet>> entry: entries) {
1399 String out = entry.getKey();
1400 List<Facet> filters = entry.getValue();
1401
1402 logger.debug("There are " + filters.size() + " filters for: " +out);
1403
1404 for (Facet filter: filters) {
1405 logger.debug(" filter: " + filter.getName());
1406 }
1407 }
1408
1409 logger.debug("######## FILTER FACETS END ########");
1410 }
1411
1412
1413 protected void destroyState(String id, Object context) {
1414 State s = getState(context, id);
1415 s.endOfLife(this, context);
1416 }
1417
1418
1419 /**
1420 * Calls endOfLife() for each state in the list <i>ids</i>.
1421 *
1422 * @param ids The State IDs that should be destroyed.
1423 * @param context The FLYSContext.
1424 */
1425 protected void destroyStates(List<String> ids, Object context) {
1426 for (int i = 0, num = ids.size(); i < num; i++) {
1427 destroyState(ids.get(i), context);
1428 }
1429 }
1430
1431
1432 /**
1433 * Destroy the states.
1434 */
1435 @Override
1436 public void endOfLife(Object context) {
1437 logger.info("FLYSArtifact.endOfLife: " + identifier());
1438
1439 ArrayList<String> ids = (ArrayList<String>) getPreviousStateIds();
1440 ArrayList<String> toDestroy = (ArrayList<String>) ids.clone();
1441
1442 toDestroy.add(getCurrentStateId());
1443
1444 destroyStates(toDestroy, context);
1445 }
1446
1447
1448 /**
1449 * Determines Facets initial disposition regarding activity (think of
1450 * selection in Client ThemeList GUI). This will be checked one time
1451 * when the facet enters a collections describe document.
1452 *
1453 * @param facetName name of the facet.
1454 * @param outputName name of the output.
1455 * @param index index of the facet.
1456 *
1457 * @return 1 if wished to be initally active, 0 if not. FLYSArtifact
1458 * defaults to "1".
1459 */
1460 public int getInitialFacetActivity(
1461 String outputName,
1462 String facetName,
1463 int index
1464 )
1465 {
1466 return 1;
1467 }
1468 }
1469 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org