1 /*******************************************************************************
2 * Imixs Workflow
3 * Copyright (C) 2001, 2011 Imixs Software Solutions GmbH,
4 * http://www.imixs.com
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You can receive a copy of the GNU General Public
17 * License at http://www.gnu.org/licenses/gpl.html
18 *
19 * Project:
20 * http://www.imixs.org
21 * http://java.net/projects/imixs-workflow
22 *
23 * Contributors:
24 * Imixs Software Solutions GmbH - initial API and implementation
25 * Ralph Soika - Software Developer
26 *******************************************************************************/
27
28 package org.imixs.workflow;
29
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Date;
33 import java.util.HashMap;
34 import java.util.Hashtable;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.Vector;
40 import java.util.logging.Logger;
41
42 /**
43 * This Class defines a ValueObject to be used to exchange Datastructures used
44 * by the org.imixs.workflow Framework. Every component of this framework should
45 * use this wrapper class to easy transport workflowrelevant data between the
46 * different workflow modules. ValueObjects, particular in J2EE Applications,
47 * have the advantage to improve perfomance of remote methode calls and also
48 * enables a flexibly datastructure. A ItemCollection contains various Items
49 * (attributes). Every Item exist of a Name and a Value. Internal every Value is
50 * stored inside a Vector Class. So it is also possible to store
51 * multivalueobjects. The ItemCollection wraps the
52 * <code>java.util.Hashtable</code> Class and implements the
53 * <code>java.io.Serializable</code> Interface, so the ValeOject can also be
54 * serialised inside a remote methode call.
55 *
56 *
57 * @author Ralph Soika
58 * @version 2.0
59 * @see org.imixs.workflow.WorkflowManager
60 */
61
62 public class ItemCollection implements java.io.Serializable {
63 private static Logger logger = Logger.getLogger("org.imixs.workflow");
64
65 private Map hash = new Hashtable();
66
67 /**
68 * Creates a empty ItemCollection
69 *
70 */
71 public ItemCollection() {
72 super();
73 }
74
75 /**
76 * Creates a new ItemCollection and transfers all Objects of map as new
77 * values.
78 *
79 * @param map
80 */
81 public ItemCollection(Map map) {
82 Iterator it = map.entrySet().iterator();
83 while (it.hasNext()) {
84 Map.Entry entry = (Map.Entry) it.next();
85 this.replaceItemValue(entry.getKey().toString(), entry.getValue());
86 }
87 }
88
89 public boolean equals(Object o) {
90 if (!(o instanceof ItemCollection))
91 return false;
92 return hash.equals(((ItemCollection) o).getAllItems());
93 }
94
95 /**
96 * returns the Value of a single Item inside the ItemCollection. If the item
97 * has no value, this method returns an empty vector. If no item with the
98 * specified name exists, this method returns an empty vector. It does not
99 * throw an exception. The ItemName is not case sensitive. Use hasItem to
100 * verify the existence of an item.
101 *
102 * @param aName
103 * The name of an item.
104 * @return The value or values contained in the item. The data type of the
105 * value depends on the data type of the item.
106 *
107 */
108 public List getItemValue(String aName) {
109 aName = aName.toLowerCase();
110 Object o = hash.get(aName);
111 if (o == null)
112 return new Vector();
113 else {
114 Vector v = (Vector) o;
115 // scan vector for null values
116 for (int i = 0; i < v.size(); i++) {
117 if (v.get(i) == null)
118 v.remove(i);
119 }
120 return v;
121 }
122 }
123
124 /**
125 * Returns the value of an item with a single text value. If the item has no
126 * value or the value is numeric or non text, this method returns an empty
127 * String. If no item with the specified name exists, this method returns an
128 * empty String. It does not throw an exception. If the item has multiple
129 * values, this method returns the first value. The ItemName is not case
130 * sensitive. Use hasItem to verify the existence of an item.
131 *
132 * @param aName
133 * The name of an item.
134 * @return The value of the item
135 *
136 */
137 public String getItemValueString(String aName) {
138
139 aName = aName.toLowerCase();
140 List v = (List) getItemValue(aName);
141 if (v.size() == 0)
142 return "";
143 else {
144 // verify if value is null
145 Object o = v.get(0);
146 if (o == null)
147 return "";
148 else
149 return (String) o;
150 }
151
152 }
153
154 /**
155 * Returns the value of an item with a single numeric value. If the item has
156 * no value or the value is no Integer, or empty, this method returns 0. If
157 * no item with the specified name exists, this method returns 0. It does
158 * not throw an exception. If the item has multiple values, this method
159 * returns the first value. The ItemName is not case sensitive. Use hasItem
160 * to verify the existence of an item.
161 *
162 * @param aName
163 * @return integer value
164 *
165 */
166 public int getItemValueInteger(String aName) {
167 try {
168 aName = aName.toLowerCase();
169 Vector v = (Vector) getItemValue(aName);
170 if (v.size() == 0)
171 return 0;
172
173 String sValue = v.firstElement().toString();
174 return new Double(sValue).intValue();
175 } catch (NumberFormatException e) {
176 return 0;
177 } catch (ClassCastException e) {
178 return 0;
179 }
180 }
181
182 /**
183 * Returns the value of an item with a single Date value. If the item has no
184 * value or the value is no Date, or empty, this method returns null. If no
185 * item with the specified name exists, this method returns null. It does
186 * not throw an exception. If the item has multiple values, this method
187 * returns the first value. The ItemName is not case sensitive. Use hasItem
188 * to verify the existence of an item.
189 *
190 * @param aName
191 * @return Date value
192 *
193 */
194 public Date getItemValueDate(String aName) {
195 try {
196 aName = aName.toLowerCase();
197 Vector v = (Vector) getItemValue(aName);
198 if (v.size() == 0)
199 return null;
200
201 Object o = v.firstElement();
202 if (!(o instanceof Date))
203 return null;
204
205 return (Date) o;
206 } catch (ClassCastException e) {
207 return null;
208 }
209 }
210
211 /**
212 * Returns the value of an item with a single numeric value. If the item has
213 * no value or the value is no Double, or empty, this method returns 0.0. If
214 * no item with the specified name exists, this method returns 0.0. It does
215 * not throw an exception. If the item has multiple values, this method
216 * returns the first value. The Itemname is not case sensetive. Use hasItem
217 * to verify the existence of an item.
218 *
219 * @param aName
220 * @return double value
221 *
222 */
223 public double getItemValueDouble(String aName) {
224 try {
225 aName = aName.toLowerCase();
226 Vector v = (Vector) getItemValue(aName);
227 if (v.size() == 0)
228 return 0.0;
229 else
230 return ((Double) v.firstElement()).doubleValue();
231 } catch (ClassCastException e) {
232 return 0.0;
233 }
234 }
235
236 /**
237 * Returns the boolean value of an item. If the item has no value or the
238 * value is no boolean, or empty, this method returns false. If no item with
239 * the specified name exists, this method returns false. It does not throw
240 * an exception. If the item has multiple values, this method returns the
241 * first value. The Itemname is not case sensitive. Use hasItem to verify
242 * the existence of an item.
243 *
244 * @param aName
245 * @return boolean value
246 *
247 */
248 public boolean getItemValueBoolean(String aName) {
249 try {
250 aName = aName.toLowerCase();
251 Vector v = (Vector) getItemValue(aName);
252 if (v.size() == 0)
253 return false;
254 String sValue = v.firstElement().toString();
255 // return new Boolean(sValue).booleanValue();
256 return Boolean.valueOf(sValue);
257 } catch (ClassCastException e) {
258 return false;
259 }
260 }
261
262 /**
263 * Indicates whether an item exists in the document.
264 *
265 * @param aName
266 * The name of an item.
267 * @return true if an item with name exists in the document, false if no
268 * item with name exists in the document
269 *
270 */
271 public boolean hasItem(String aName) {
272 aName = aName.toLowerCase();
273 return (hash.get(aName) != null);
274 }
275
276 /**
277 * returns all Items of the Collection as a Map
278 *
279 * @return Map with all Items
280 */
281 public Map getAllItems() {
282 return hash;
283
284 }
285
286 /**
287 * replaces the current map object. In different to the method
288 * replaceAllItems this method overwrites the hash object and did not copy
289 * the values
290 *
291 * @param aHash
292 */
293 public void setAllItems(Map aHash) {
294 hash = aHash;
295
296 }
297
298 /**
299 * Replaces the value of an item. If the ItemCollection does not contain an
300 * item with the specified name, the method creates a new item and adds it
301 * to the ItemCollection. The ItemName is not case sensitive. Use hasItem to
302 * verify the existence of an item. All item names will be lower cased.
303 *
304 * Each item can contain a list of values (multivalue item). If a single
305 * value is provided the method creates a List with one single value
306 * (singlevalue item).
307 *
308 * If the value is null the method will remove the item. This is equal to
309 * the method call removeItem()
310 *
311 * If the ItemValue is not serializable the item will be removed.
312 *
313 *
314 * @param itemName
315 * The name of the item or items you want to replace.
316 * @param itemValue
317 * The value of the new item. The data type of the item depends
318 * upon the data type of value, and does not need to match the
319 * data type of the old item.
320 */
321 public void replaceItemValue(String itemName, Object itemValue) {
322 setItemValue(itemName, itemValue, false);
323 }
324
325 /**
326 * Appends a value to an existing item. If the ItemCollection does not
327 * contain an item with the specified name, the method creates a new item
328 * and adds it to the ItemCollection. The ItemName is not case sensitive.
329 * Use hasItem to verify the existence of an item. All item names will be
330 * lower cased.
331 *
332 * If a value list is provided the method appends each single value.
333 *
334 * If the value is null the method will remove the item. This is equal to
335 * the method call removeItem()
336 *
337 * If the ItemValue is not serializable the item will be removed.
338 *
339 *
340 * @param itemName
341 * The name of the item or items you want to replace.
342 * @param itemValue
343 * The value of the new item. The data type of the item depends
344 * upon the data type of value, and does not need to match the
345 * data type of the old item.
346 */
347 public void appendItemValue(String itemName, Object itemValue) {
348 setItemValue(itemName, itemValue, true);
349 }
350
351 /**
352 * Helper method to replace an ItemValue.
353 *
354 * @param itemName
355 * - name of the value
356 * @param itemValue
357 * - value
358 * @param append
359 * - true if the value should be appended to an existing list
360 */
361 private void setItemValue(String itemName, Object itemValue, boolean append) {
362 if (itemName == null)
363 return;
364 // lower case itemname
365 itemName = itemName.toLowerCase();
366
367 // test if value is null
368 if (itemValue == null) {
369 // remove the item
370 this.removeItem(itemName);
371 return;
372 }
373
374 // test if value is serializable
375 if (!(itemValue instanceof java.io.Serializable)) {
376 logger.warning("[ItemCollection] replaceItemValue '" + itemName
377 + "': Object no Serializable!");
378 this.removeItem(itemName);
379 return;
380 }
381
382 // test if value is a list and remove null values
383 if (itemValue instanceof List) {
384 // scan List for null values and remove them
385 for (int i = 0; i < ((List<?>) itemValue).size(); i++) {
386 if (((List<?>) itemValue).get(i) == null)
387 ((List<?>) itemValue).remove(i);
388 }
389 } else {
390 // create an instance of Vector
391 Vector v = new Vector();
392 v.addElement(itemValue);
393 itemValue = v;
394 }
395
396 // now itemValue is of instance List
397
398 // replace item value?
399 if (append) {
400 // append item value
401 List newValueList = getItemValue(itemName);
402 for (Object o : (List) itemValue) {
403 newValueList.add(o);
404 }
405 hash.put(itemName, newValueList);
406 } else
407 hash.put(itemName, itemValue);
408
409 }
410
411 /**
412 * Replaces all items specified in the map with new items, which are
413 * assigned to the specified values inside the map
414 *
415 * @param map
416 */
417 public void replaceAllItems(Map map) {
418 Iterator it = map.entrySet().iterator();
419 while (it.hasNext()) {
420 Map.Entry entry = (Map.Entry) it.next();
421 replaceItemValue(entry.getKey().toString(), entry.getValue());
422 }
423
424 }
425
426 /**
427 * removes a attribute from the item collection
428 *
429 * @param name
430 */
431 public void removeItem(String name) {
432 name = name.toLowerCase();
433 this.getAllItems().remove(name);
434 }
435
436 /**
437 * This method adds a single file to the ItemCollection. files will be
438 * stored into the property $file.
439 *
440 * @param data
441 * - byte array with file data
442 * @param fileName
443 * - name of the file attachment
444 * @param contentType
445 * - the contenttype (e.g. 'Text/HTML')
446 *
447 */
448 public void addFile(byte[] data, String fileName, String contentType) {
449 if (data != null) {
450 Vector<Object> vectorFileInfo = null;
451
452 // IE includes '\' characters! so remove all these characters....
453 if (fileName.indexOf('\\') > -1)
454 fileName = fileName.substring(fileName.lastIndexOf('\\') + 1);
455 if (fileName.indexOf('/') > -1)
456 fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
457
458 if (contentType == null || "".equals(contentType))
459 contentType = "application/unknown";
460
461 // Store files using a hashmap....
462 HashMap mapFiles = null;
463 List vFiles = getItemValue("$file");
464 if (vFiles != null && vFiles.size() > 0)
465 mapFiles = (HashMap) vFiles.get(0);
466 else
467 mapFiles = new HashMap();
468
469 // existing file will be overridden!
470 vectorFileInfo = (Vector) mapFiles.get(fileName);
471 vectorFileInfo = new Vector<Object>();
472 // put file in a vector containing the byte array and also the
473 // content type
474 vectorFileInfo.add(contentType);
475 vectorFileInfo.add(data);
476 mapFiles.put(fileName, vectorFileInfo);
477 replaceItemValue("$file", mapFiles);
478 }
479 }
480
481 /**
482 * This method removes a single file attachment from the BlobWorkitem
483 *
484 */
485 public void removeFile(String aFilename) {
486 /* delete attachment */
487 HashMap mapFiles = null;
488 List vFiles = getItemValue("$file");
489 if (vFiles != null && vFiles.size() > 0) {
490 mapFiles = (HashMap) vFiles.get(0);
491 mapFiles.remove(aFilename);
492 replaceItemValue("$file", mapFiles);
493 }
494
495 }
496
497 /**
498 * Returns a list of file names attached to the current BlobWorkitem. File
499 * Attachments can be added using the method addFile().
500 *
501 * @return
502 */
503 public String[] getFiles() {
504 // File attachments...
505 String[] files = new String[0];
506
507 HashMap mapFiles = null;
508 List vFiles = getItemValue("$file");
509 if (vFiles != null && vFiles.size() > 0) {
510 mapFiles = (HashMap) vFiles.get(0);
511 files = new String[mapFiles.entrySet().size()];
512 Iterator iter = mapFiles.entrySet().iterator();
513 int iFileCount = 0;
514 while (iter.hasNext()) {
515 Map.Entry mapEntry = (Map.Entry) iter.next();
516 String aFileName = mapEntry.getKey().toString();
517 files[iFileCount] = aFileName;
518 iFileCount++;
519 }
520 }
521
522 return files;
523 }
524
525 public Map getItem() {
526 return new ItemAdapter(this);
527 }
528
529 public Map getItemList() {
530 return new ItemListAdapter(this);
531 }
532
533 public Map getItemListArray() {
534 return new ItemListArrayAdapter(this);
535 }
536
537 /**
538 * This class helps to addapt the behavior of a singel value item to be used
539 * in a jsf page using a expression language like this:
540 *
541 * #{mybean.item['txtMyItem']}
542 *
543 *
544 * @author rsoika
545 *
546 */
547 class ItemAdapter implements Map {
548 ItemCollection itemCollection;
549
550 public ItemAdapter() {
551 itemCollection = new ItemCollection();
552 }
553
554 public ItemAdapter(ItemCollection acol) {
555 itemCollection = acol;
556 }
557
558 public void setItemCollection(ItemCollection acol) {
559 itemCollection = acol;
560 }
561
562 /**
563 * returns a single value out of the ItemCollection if the key does not
564 * exist the method will create a value automatically
565 */
566 @SuppressWarnings("unchecked")
567 public Object get(Object key) {
568 // check if a value for this key is available...
569 // if not create a new empty value
570 if (!itemCollection.hasItem(key.toString()))
571 itemCollection.replaceItemValue(key.toString(), "");
572
573 // return first value from vector if size >0
574 List v = itemCollection.getItemValue(key.toString());
575 if (v.size() > 0)
576 return v.get(0);
577 else
578 // otherwise return null
579 return null;
580 }
581
582 /**
583 * puts a single value into the ItemCollection
584 */
585 public Object put(Object key, Object value) {
586 if (key == null)
587 return null;
588 itemCollection.replaceItemValue(key.toString(), value);
589 return value;
590 }
591
592 /* ############### Default methods ################# */
593
594 public void clear() {
595 itemCollection.getAllItems().clear();
596 }
597
598 public boolean containsKey(Object key) {
599 return itemCollection.getAllItems().containsKey(key);
600 }
601
602 public boolean containsValue(Object value) {
603 return itemCollection.getAllItems().containsValue(value);
604 }
605
606 public Set entrySet() {
607 return itemCollection.getAllItems().entrySet();
608 }
609
610 public boolean isEmpty() {
611 return itemCollection.getAllItems().isEmpty();
612 }
613
614 public Set keySet() {
615 return itemCollection.getAllItems().keySet();
616 }
617
618 public void putAll(Map m) {
619 itemCollection.getAllItems().putAll(m);
620
621 }
622
623 public Object remove(Object key) {
624 return itemCollection.getAllItems().remove(key);
625 }
626
627 public int size() {
628 return itemCollection.getAllItems().size();
629 }
630
631 public Collection values() {
632 return itemCollection.getAllItems().values();
633 }
634
635 }
636
637 /**
638 * This class helps to addapt the behavior of a multivalue item to be used
639 * in a jsf page using a expression language like this:
640 *
641 * #{mybean.item['txtMyList']}
642 *
643 *
644 * @author rsoika
645 *
646 */
647 class ItemListAdapter extends ItemAdapter {
648
649 public ItemListAdapter(ItemCollection acol) {
650 itemCollection = acol;
651 }
652
653 /**
654 * returns a multi value out of the ItemCollection if the key dos not
655 * exist the method will create a value automatical
656 */
657 public Object get(Object key) {
658 // check if a value for this key is available...
659 // if not create a new empty value
660 if (!itemCollection.hasItem(key.toString()))
661 itemCollection.replaceItemValue(key.toString(), "");
662
663 return itemCollection.getItemValue(key.toString());
664 }
665
666 }
667
668 class ItemListArrayAdapter extends ItemAdapter {
669
670 public ItemListArrayAdapter(ItemCollection acol) {
671 itemCollection = acol;
672 }
673
674 /**
675 * returns a multi value out of the ItemCollection if the key dos not
676 * exist the method will create a value automatical
677 */
678 public Object get(Object key) {
679 // check if a value for this key is available...
680 // if not create a new empty value
681 if (!itemCollection.hasItem(key.toString()))
682 itemCollection.replaceItemValue(key.toString(), "");
683 // return new ArrayList Object containing values from vector
684 ArrayList<Object> aList = new ArrayList<Object>();
685 Collection col = itemCollection.getItemValue(key.toString());
686 for (Object aEntryValue : col) {
687 aList.add(aEntryValue);
688 }
689 return aList;
690
691 }
692
693 /**
694 * puts a arraylist value into the ItemCollection
695 */
696 public Object put(Object key, Object value) {
697 if (key == null)
698 return null;
699
700 // skipp null values
701 if (value == null) {
702 itemCollection.replaceItemValue(key.toString(), new Vector());
703 return null;
704 }
705 // convert List into Vector object
706 if (value instanceof List || value instanceof Object[]) {
707 Vector v = new Vector();
708 // check type of list (array and list are supported but need
709 // to be read in different ways
710 if (value instanceof List)
711 for (Object aEntryValue : (List) value) {
712 v.add(aEntryValue);
713 }
714 else if (value instanceof Object[])
715 for (Object aEntryValue : (Object[]) value) {
716 v.add(aEntryValue);
717 }
718 itemCollection.replaceItemValue(key.toString(), v);
719 } else
720 // non convertable object!
721 itemCollection.replaceItemValue(key.toString(), value);
722
723 return value;
724 }
725 }
726 }