View Javadoc

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.jee.ejb;
29  
30  import java.util.ArrayList;
31  import java.util.Calendar;
32  import java.util.Collection;
33  import java.util.Date;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.StringTokenizer;
38  import java.util.Vector;
39  import java.util.logging.Logger;
40  
41  import javax.annotation.Resource;
42  import javax.annotation.security.DeclareRoles;
43  import javax.annotation.security.RolesAllowed;
44  import javax.ejb.LocalBean;
45  import javax.ejb.SessionContext;
46  import javax.ejb.Stateless;
47  import javax.persistence.EntityManager;
48  import javax.persistence.FlushModeType;
49  import javax.persistence.PersistenceContext;
50  import javax.persistence.Query;
51  
52  import org.imixs.workflow.ItemCollection;
53  import org.imixs.workflow.exceptions.AccessDeniedException;
54  import org.imixs.workflow.exceptions.InvalidAccessException;
55  
56  import org.imixs.workflow.jee.jpa.CalendarItem;
57  import org.imixs.workflow.jee.jpa.DoubleItem;
58  import org.imixs.workflow.jee.jpa.Entity;
59  import org.imixs.workflow.jee.jpa.EntityIndex;
60  import org.imixs.workflow.jee.jpa.IntegerItem;
61  import org.imixs.workflow.jee.jpa.ReadAccess;
62  import org.imixs.workflow.jee.jpa.TextItem;
63  import org.imixs.workflow.jee.jpa.WriteAccess;
64  
65  /**
66   * The EntityService is used to save and load instances of ItemCollections into
67   * a Database. The EntityService throws an AccessDeniedException if the
68   * CallerPrincipal is not allowed to save or read a specific ItemCollection from
69   * the database. So the EntityService can be used to save business objects into
70   * a database with individual read- or writeAccess restrictions.
71   * <p>
72   * The Bean holds an instance of an EntityPersistenceManager for the persistence
73   * unit 'org.imixs.workflow.jee.ejb' to manage the following Entity EJBs:
74   * <ul>
75   * <li>Entity,
76   * <li>EntityIndex,
77   * <li>TextIndex,
78   * <li>IntegerIndex,
79   * <li>DoubleIndex,
80   * <li>CalendarIndex,
81   * <li>ReadAccessEntity
82   * <li>WriteAccessEntity
83   * </ul>
84   * < These Entity EJBs are used to store the attributes of a ItemCollection into
85   * the connected database.
86   * <p>
87   * The save() method persists any instance of an ItemCollection. If a
88   * ItemCollection is saved the first time the EntityServiceBean generates the
89   * attribute $uniqueid which will be included in the ItemCollection returned by
90   * this method. If a ItemCollection was saved before the method updates the
91   * corresponding Entity Object.
92   * <p>
93   * The load() and findAllEntities() methods are used to read ItemCollections
94   * from the database. The remove() Method deletes a saved ItemCollection from
95   * the database.
96   * <p>
97   * All methods expect and return Instances of the object
98   * org.imixs.workflow.ItemCollection which is no entity EJB. So these objects
99   * are not managed by any instance of an EntityPersistenceManager.
100  * <p>
101  * A collection of ItemCollections can be read using the findAllEntites() method
102  * using EQL syntax.
103  * <p>
104  * This EntityServiceBean has methods to brand out different Attributes of a
105  * ItemCollection into external properties (TextIndex, IntegerIndex,
106  * DoubleIndex, CalendarIndex) With these functionality a client can query a set
107  * of ItemCollections later with EJB Query Language (EQL).
108  * <p>
109  * To define which attributes should be expended into external properties the
110  * Session EJB supports the method addIndex(). This method creates an index
111  * entry and ensures that every Entity saved before will be updated and future
112  * calls of the save() method will care about the new index to separate
113  * attributes into the Index Properties.
114  * <p>
115  * So each call of the save method automatically disassemble a ItemCollection
116  * into predefined Index properties and stores attributes which have an Index
117  * into a corresponding IndexData Object (TextIndex, IntegerIndex, DoubleIndex,
118  * CalendarIndex). On the other hand the load() method reassembles the
119  * ItemCollection including all attributes form the data property an any
120  * external index property. The EntiyService did not allow to access a managed
121  * Entity EJB directly. This simplifies the usage as a client works only with
122  * ItemCollections and so there is no need to handle managed and detached entity
123  * EJB instances.
124  * <p>
125  * Additional to the basic functionality to save and load instances of the
126  * object org.imixs.workflow.ItemCollection the method also manages the read-
127  * and writeAccess for each instance of an ItemCollection. Therefore the save()
128  * method scans an ItemCollection for the attributes '$ReadAccess' and
129  * '$WriteAccess' and saves these informations into the Entity EJBs
130  * ReadAccessEntity and WriteAccessEntity controlled by each instance of the EJB
131  * Entity. The EntityServiceBean implementation verifies in each call of the
132  * save() load(), remove() and findAllEntities() methods if the current
133  * callerPrincipal is granted to the affected entities. If an ItemCollection was
134  * saved with read- or writeAccess the access to an Instance of a saved
135  * ItemCollection will be protected for a callerPrincipal with missing read- or
136  * writeAccess. The default Read- and WriteAccess attributes '$ReadAccess' and
137  * '$WriteAccess' can be overwritten by the environment settings
138  * 'READ_ACCESS_FIELDS' and 'WRITE_ACCESS_FIELDS'
139  * <p>
140  * Some useful links about the oneToMany Relationship
141  * http://www.avaje.org/manydatatypes.html
142  * http://thomas-schuett.de/2008/11/14/ordered-lists-in-jpa-do-it-yourself/
143  * http:
144  * //javaehrsson.blogspot.com/2005/10/ejb3-onetomany-and-orderby-set-versus.html
145  * http://forums.java.net/jive/thread.jspa?threadID=30869&start=0&tstart=0
146  * 
147  * @see org.imixs.workflow.jee.ejb.EntityPersistenceManager
148  * @author rsoika
149  * @version 1.0
150  * 
151  */
152 
153 @DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS",
154 		"org.imixs.ACCESSLEVEL.READERACCESS",
155 		"org.imixs.ACCESSLEVEL.AUTHORACCESS",
156 		"org.imixs.ACCESSLEVEL.EDITORACCESS",
157 		"org.imixs.ACCESSLEVEL.MANAGERACCESS" })
158 @RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS",
159 		"org.imixs.ACCESSLEVEL.READERACCESS",
160 		"org.imixs.ACCESSLEVEL.AUTHORACCESS",
161 		"org.imixs.ACCESSLEVEL.EDITORACCESS",
162 		"org.imixs.ACCESSLEVEL.MANAGERACCESS" })
163 @Stateless
164 @LocalBean
165 public class EntityService implements EntityServiceRemote {
166 
167 	public static final int TYP_TEXT = 0;
168 
169 	public static final int TYP_INT = 1;
170 
171 	public static final int TYP_DOUBLE = 2;
172 
173 	public static final int TYP_CALENDAR = 3;
174 
175 	public static final String ACCESSLEVEL_NOACCESS = "org.imixs.ACCESSLEVEL.NOACCESS";
176 
177 	public static final String ACCESSLEVEL_READERACCESS = "org.imixs.ACCESSLEVEL.READERACCESS";
178 
179 	public static final String ACCESSLEVEL_AUTHORACCESS = "org.imixs.ACCESSLEVEL.AUTHORACCESS";
180 
181 	public static final String ACCESSLEVEL_EDITORACCESS = "org.imixs.ACCESSLEVEL.EDITORACCESS";
182 
183 	public static final String ACCESSLEVEL_MANAGERACCESS = "org.imixs.ACCESSLEVEL.MANAGERACCESS";
184 
185 	public static final String UNIQUEID = "$uniqueid";
186 
187 	public static final String USER_GROUP_LIST = "org.imixs.USER.GROUPLIST";
188 
189 	private static Logger logger = Logger.getLogger("org.imixs.workflow");
190 
191 	@Resource
192 	SessionContext ctx;
193 
194 	@Resource(name = "READ_ACCESS_FIELDS")
195 	private String readAccessFields = "";
196 	@Resource(name = "WRITE_ACCESS_FIELDS")
197 	private String writeAccessFields = "";
198 	@Resource(name = "ACCESS_ROLES")
199 	private String accessRoles = "";
200 	@Resource(name = "DISABLE_OPTIMISTIC_LOCKING")
201 	private Boolean disbaleOptimisticLocking = false;
202 
203 	@PersistenceContext(unitName = "org.imixs.workflow.jee.jpa")
204 	private EntityManager manager;
205 
206 	
207 	/**
208 	 * Returns additional AccessRoles defined for the EJB instance
209 	 * 
210 	 * @return
211 	 */
212 	public String getAccessRoles() {
213 		return accessRoles;
214 	}
215 
216 	/**
217 	 * Returns additional ReadAccessFields defined for the EJB instance.
218 	 * Default=$ReadAccess
219 	 * 
220 	 * @return
221 	 */
222 	public String getReadAccessFields() {
223 		return readAccessFields;
224 	}
225 
226 	/**
227 	 * Returns additional WriteAccessFields defined for the EJB instance.
228 	 * Default=$WriteAccess
229 	 * 
230 	 * @return
231 	 */
232 	public String getWriteAccessFields() {
233 		return writeAccessFields;
234 	}
235 
236 	/**
237 	 * This method returns a list of user names, roles and application groups
238 	 * the user belongs to.
239 	 * 
240 	 * @return
241 	 */
242 	public List<String> getUserNameList() {
243 	
244 		List<String> userNameList = new Vector<String>();
245 	
246 		// Begin with the username
247 		userNameList.add(ctx.getCallerPrincipal().getName().toString());
248 		// now construct role list
249 		String roleList = "org.imixs.ACCESSLEVEL.READERACCESS,org.imixs.ACCESSLEVEL.AUTHORACCESS,org.imixs.ACCESSLEVEL.EDITORACCESS,"
250 				+ accessRoles;
251 		// and add each role the user is in to the list
252 		StringTokenizer roleListTokens = new StringTokenizer(roleList, ",");
253 		while (roleListTokens.hasMoreTokens()) {
254 			try {
255 				String testRole = roleListTokens.nextToken().trim();
256 				if (!"".equals(testRole) && ctx.isCallerInRole(testRole))
257 					userNameList.add(testRole);
258 			} catch (Exception e) {
259 				// no operation - Role simply not defined
260 				// this could be an configuration/test issue and need not to be
261 				// handled as an error
262 			}
263 		}
264 	
265 		// read dynamic user roles from the ContextData (if provided) and add
266 		// them to the query....
267 		String[] applicationGroups = getUserGroupList();
268 		if (applicationGroups != null)
269 			for (String auserRole : applicationGroups)
270 				userNameList.add(auserRole);
271 	
272 		return userNameList;
273 	}
274 
275 	/**
276 	 * This Method saves an ItemCollection into a database. If the
277 	 * ItemCollection is saved the first time the method generates a uniqueID
278 	 * ('$uniqueid') which can be used to identify the ItemCollection by its ID.
279 	 * If the ItemCollection was saved before the method updates the
280 	 * ItemCollection stored in the database. The Method returns an updated
281 	 * instance of the ItemCollection containing the attributes $modified,
282 	 * $created, and $uniqueid
283 	 * <p>
284 	 * The method throws an AccessDeniedException if the CallerPrincipal is not
285 	 * allowed to save or update the ItemCollection in the database. The
286 	 * CallerPrincipial should have at least the access Role
287 	 * org.imixs.ACCESSLEVEL.AUTHORACCESS
288 	 * 
289 	 * @param ItemCollection
290 	 *            to be saved
291 	 * @return updated ItemCollection
292 	 */
293 	public ItemCollection save(ItemCollection itemcol)
294 			throws AccessDeniedException {
295 
296 		Entity activeEntity = null;
297 		ItemCollection slimItemCollection = null;
298 
299 		/*
300 		 * First we get a List of all existing Indices
301 		 * 
302 		 * if the FlushModeType=AUTO (which is the default) the getIndices()
303 		 * call will force an internal flush()
304 		 */
305 		Collection<EntityIndex> entityIndexCache = getIndices();
306 
307 		// Now set flush Mode to COMMIT
308 		manager.setFlushMode(FlushModeType.COMMIT);
309 
310 		// check if a $uniqueid is available
311 		String sID = itemcol.getItemValueString(UNIQUEID);
312 		if (!"".equals(sID)) {
313 			// yes so we can try to find the Entity by its primary key
314 			activeEntity = manager.find(Entity.class, sID);
315 			if (activeEntity == null) {
316 				logger.fine("[EntityService] Entity '" + sID + "' not found!");
317 			} else {
318 				/*
319 				 * try { // #issue 102 - we try to flush :-/
320 				 * logger.info("[EntityServiceBean] #issue 102 - refresh() - "
321 				 * +sID); manager.refresh(activeEntity); } catch
322 				 * (EntityNotFoundException ex) { logger.warning(
323 				 * "[EntityServiceBean] #issue 102 - refresh() EntityNotFoundException"
324 				 * ); activeEntity = null; }
325 				 */
326 			}
327 
328 		}
329 
330 		// did entity exist?
331 		if (activeEntity == null) {
332 			// entity no found in database create new instance using the
333 			// provided id
334 			// Test if user is allowed to create Entities....
335 			if (!(ctx.isCallerInRole(ACCESSLEVEL_MANAGERACCESS)
336 					|| ctx.isCallerInRole(ACCESSLEVEL_EDITORACCESS) || ctx
337 						.isCallerInRole(ACCESSLEVEL_AUTHORACCESS))) {
338 				throw new AccessDeniedException(
339 						"[EntityService] You are not allowed to perform this operation");
340 			}
341 			// create new one with the provided id
342 			activeEntity = new Entity(sID);
343 			// if $Created is provided than overtake this information
344 			Date datCreated = itemcol.getItemValueDate("$Created");
345 			if (datCreated != null) {
346 				Calendar cal = Calendar.getInstance();
347 				cal.setTime(datCreated);
348 				// Overwrite Creation Date
349 				activeEntity.setCreated(cal);
350 			}
351 
352 			// now persist the new Entity
353 			manager.persist(activeEntity);
354 
355 		} else {
356 			// activeEntity exists - verify if current user has write- and
357 			// readaccess
358 			if (!isCallerAuthor(activeEntity) || !isCallerReader(activeEntity)) {
359 				throw new AccessDeniedException(
360 						"[EntityService] You are not allowed to perform this operation");
361 			}
362 		}
363 
364 		// after all activeEntity is now managed through the persistence
365 		// manager!
366 
367 		// remove property $isauthor
368 		itemcol.removeItem("$isauthor");
369 
370 		String aType = itemcol.getItemValueString("type");
371 		if ("".equals(aType))
372 			aType = "Entity";
373 		activeEntity.setType(aType);
374 
375 		// update the standard attributes $modified $created and $uniqueid
376 		Calendar cal = Calendar.getInstance();
377 
378 		itemcol.replaceItemValue("$uniqueid", activeEntity.getId());
379 		itemcol.replaceItemValue("$modified", cal.getTime());
380 		itemcol.replaceItemValue("$created", activeEntity.getCreated()
381 				.getTime());
382 		// create a new Instance of this ItemCollection
383 		slimItemCollection = new ItemCollection(itemcol.getAllItems());
384 
385 		// update read- and writeAccess List
386 		updateReadAccessList(slimItemCollection, activeEntity);
387 		updateWriteAccessList(slimItemCollection, activeEntity);
388 
389 		explodeEntity(slimItemCollection, activeEntity, entityIndexCache);
390 
391 		// finally update the data field and store the item map object
392 		activeEntity.setData(slimItemCollection.getAllItems());
393 
394 		// verify and update the author access and add again the property
395 		// '$isauthor'
396 		itemcol.replaceItemValue("$isauthor", isCallerAuthor(activeEntity));
397 
398 		return itemcol;
399 	}
400 
401 	/**
402 	 * This method loads an ItemCollection from the Database. The method expects
403 	 * a valid $unqiueID to identify the ItemCollection saved before into the
404 	 * database. The method returns null if no ItemCollection with the
405 	 * corresponding ID exists.
406 	 * <p>
407 	 * The method checks if the CallerPrincipal has read access to
408 	 * ItemCollection stored in the database. If not the method returns null.
409 	 * The method dose not throw an AccessDeniedException if the user is not
410 	 * allowed to read the entity to prevent a aggressor with informations about
411 	 * the existence of that specific ItemCollection.
412 	 * <p>
413 	 * CallerPrincipial should have at least the access Role
414 	 * org.imixs.ACCESSLEVEL.READACCESS
415 	 * 
416 	 * @param id
417 	 *            - the $unqiueid of the ItemCollection to be loaded
418 	 * @return ItemCollection object or null if the ItemColleciton dose not
419 	 *         exist or the CallerPrincipal hat insufficient read access.
420 	 * 
421 	 */
422 	public ItemCollection load(String id) {
423 		Entity activeEntity = null;
424 		activeEntity = manager.find(Entity.class, id);
425 
426 		// implode the ItemCollection object
427 		if (activeEntity != null && isCallerReader(activeEntity)) {
428 			/*
429 			 * try { manager.refresh(activeEntity); } catch
430 			 * (EntityNotFoundException ex) { logger.warning(
431 			 * "[EntityServiceBean] #issue 102 - refresh() EntityNotFoundException"
432 			 * ); activeEntity = null; return null; }
433 			 */
434 			return implodeEntity(activeEntity);
435 		} else
436 			return null;
437 	}
438 
439 	/**
440 	 * This method removes an ItemCollection from the database. If the
441 	 * CallerPrincipal is not allowed to access the ItemColleciton the method
442 	 * throws an AccessDeniedException.
443 	 * <p>
444 	 * The CallerPrincipial should have at least the access Role
445 	 * org.imixs.ACCESSLEVEL.AUTHORACCESS
446 	 * <p>
447 	 * Also the method removes all existing relation ships of the entity. This
448 	 * is necessary becaus of the FetchType.LAZY used for most relations.
449 	 * 
450 	 * 
451 	 * @param ItemCollection
452 	 *            to be removed
453 	 * @throws AccessDeniedException
454 	 */
455 	public void remove(ItemCollection itemcol) throws AccessDeniedException {
456 		Entity activeEntity = null;
457 		String sID = itemcol.getItemValueString("$uniqueid");
458 		activeEntity = manager.find(Entity.class, sID);
459 
460 		if (activeEntity != null) {
461 			if (!isCallerReader(activeEntity) || !isCallerAuthor(activeEntity))
462 				throw new AccessDeniedException(
463 						"[EntityService] You are not allowed to perform this operation");
464 
465 			// remove current TextItem List
466 			for (TextItem aItem : activeEntity.getTextItems())
467 				manager.remove(aItem);
468 			activeEntity.getTextItems().clear();
469 
470 			// remove current CalendarItem List
471 			for (CalendarItem aItem : activeEntity.getCalendarItems())
472 				manager.remove(aItem);
473 			activeEntity.getCalendarItems().clear();
474 
475 			// remove current IntegerItem List
476 			for (IntegerItem aItem : activeEntity.getIntegerItems())
477 				manager.remove(aItem);
478 			activeEntity.getIntegerItems().clear();
479 
480 			// remove current IntegerItem List/
481 			for (DoubleItem aItem : activeEntity.getDoubleItems())
482 				manager.remove(aItem);
483 			activeEntity.getDoubleItems().clear();
484 
485 			// remove current WriteAccess List
486 			for (WriteAccess aItem : activeEntity.getWriteAccessList())
487 				manager.remove(aItem);
488 			activeEntity.getWriteAccessList().clear();
489 
490 			// remove current ReadAccess List
491 			for (ReadAccess aItem : activeEntity.getReadAccessList())
492 				manager.remove(aItem);
493 			activeEntity.getReadAccessList().clear();
494 
495 			// remove entity...
496 			manager.remove(activeEntity);
497 
498 		} else
499 			throw new AccessDeniedException("[EntityService] invalid $uniqueid");
500 	}
501 
502 	/**
503 	 * Adds an Index for a property provided by ItemCollection objects. An
504 	 * existing Index can be used to select ItemCollections using a JPQL
505 	 * statement. @see findEntitesByQuery
506 	 * 
507 	 * The method throws an AccessDeniedException if the CallerPrinciapal is not
508 	 * in the role org.imixs.ACCESSLEVEL.MANAGERACCESS.
509 	 * 
510 	 * @param name
511 	 *            of a property (not case sensetive)
512 	 * @param ityp
513 	 *            - Type of EntityIndex
514 	 * @throws AccessDeniedException
515 	 */
516 	public void addIndex(String stitel, int ityp) throws AccessDeniedException {
517 		// lower case title
518 		stitel = stitel.toLowerCase();
519 
520 		// check if index already exists?
521 		EntityIndex activeEntityIndex = manager.find(EntityIndex.class, stitel);
522 		if (activeEntityIndex != null)
523 			return;
524 
525 		// Check security roles
526 		// only MANAGERACCESS is allowed to add index and rebuild existing
527 		// entities!
528 		if (ctx.isCallerInRole("org.imixs.ACCESSLEVEL.MANAGERACCESS") == false)
529 			throw new AccessDeniedException(
530 					"[EntityService] You are not allowed to add index fields");
531 
532 		// add new index
533 		logger.info("[EntityServiceBean] add new Index: " + stitel + ":" + ityp);
534 		activeEntityIndex = new EntityIndex(stitel, ityp);
535 		manager.persist(activeEntityIndex);
536 
537 		// 1.) find all existing entities
538 
539 		Collection<Entity> entityList = null;
540 		Query q = manager.createQuery("SELECT entity FROM Entity entity");
541 		entityList = q.getResultList();
542 		logger.info("[EntityServiceBean] found " + entityList.size()
543 				+ " existing entities. Starting update...");
544 		updateAllEntityIndexFields(entityList, stitel);
545 		logger.info("[EntityServiceBean] index update completed");
546 
547 	}
548 
549 	/**
550 	 * This method removes an existing index from the current indexlist and
551 	 * updates existing entities. Notice that the index field name will be
552 	 * lowercased! Each EQL statement should use lower cased fieldnames!
553 	 * 
554 	 * The method checks if the Caller is in Role
555 	 * "org.imixs.ACCESSLEVEL.MANAGERACCESS". Since the caller is no Manager
556 	 * there is no guarantee that the caller can update all existing Entities as
557 	 * maybe he can not read all entities.
558 	 * 
559 	 * All existing Entities will be reorderd by this method call. So the method
560 	 * can take a long time to proceed
561 	 * 
562 	 * @param stitel
563 	 *            - will be automatical lowercased!
564 	 * @throws AccessDeniedException
565 	 * 
566 	 */
567 	public void removeIndex(String stitel) throws AccessDeniedException {
568 		int indexType = 0;
569 		// lower case title
570 		stitel = stitel.toLowerCase();
571 
572 		// check if index already exists?
573 		EntityIndex activeEntityIndex = manager.find(EntityIndex.class, stitel);
574 		if (activeEntityIndex == null)
575 			return;
576 
577 		// Check security roles
578 		if (ctx.isCallerInRole("org.imixs.ACCESSLEVEL.MANAGERACCESS") == false)
579 			throw new AccessDeniedException(
580 					"[EntityService] You are not allowed to add index fields");
581 
582 		indexType = activeEntityIndex.getTyp();
583 
584 		logger.info("[EntityService] remove Index: " + stitel + ":" + indexType);
585 
586 		// remove index
587 		manager.remove(activeEntityIndex);
588 
589 		// now update all entity index fields
590 		// 1.) find all existing entities
591 		Collection<Entity> entityList = null;
592 		// preselect worktiems which are really affected
593 		String query = "SELECT wi FROM Entity wi ";
594 		switch (indexType) {
595 		case EntityIndex.TYP_CALENDAR:
596 			query += " JOIN wi.calendarItems AS i1 WHERE i1.itemName='"
597 					+ stitel + "'";
598 			break;
599 		case EntityIndex.TYP_DOUBLE:
600 			query += " JOIN wi.doubleItems AS i1 WHERE i1.itemName='" + stitel
601 					+ "'";
602 			break;
603 		case EntityIndex.TYP_INT:
604 			query += " JOIN wi.integerItems AS i1 WHERE i1.itemName='" + stitel
605 					+ "'";
606 			break;
607 
608 		default:
609 			query += " JOIN wi.textItems AS i1 WHERE i1.itemName='" + stitel
610 					+ "'";
611 			break;
612 		}
613 
614 		logger.info("[EntityServiceBean] remove Index - update query=" + query);
615 		Query q = manager.createQuery(query);
616 
617 		entityList = q.getResultList();
618 		logger.info("[EntityServiceBean] found " + entityList.size()
619 				+ " affected entities. Starting update...");
620 		updateAllEntityIndexFields(entityList, null);
621 		logger.info("[EntityServiceBean] index update completed");
622 
623 	}
624 
625 	/**
626 	 * The method returns a list of EntityIndex which defines external
627 	 * properties
628 	 * 
629 	 * @return
630 	 */
631 	public Collection<EntityIndex> getIndices() {
632 		// Caching
633 		logger.finer("getIndiecies....");
634 
635 		Query q = manager
636 				.createQuery("SELECT entityindex FROM EntityIndex entityindex");
637 		return q.getResultList();
638 	}
639 
640 	/**
641 	 * The method returns a collection of ItemCollections. The method expects an
642 	 * valid jPQL statement. The method returns only ItemCollections which are
643 	 * readable by the CallerPrincipal. With the startpos and count parameters
644 	 * it is possible to read chunks of entities. The jPQL Statement must match
645 	 * the conditions of the JPA Object Class Entity
646 	 * 
647 	 * @param query
648 	 * @param startpos
649 	 * @param count
650 	 * @return
651 	 * @throws InvalidAccessException
652 	 * 
653 	 * @see org.imixs.workfow.jee.jpa.Entity
654 	 */
655 	public Collection<ItemCollection> findAllEntities(String query,
656 			int startpos, int count) throws InvalidAccessException {
657 		Vector<ItemCollection> vectorResult = new Vector<ItemCollection>();
658 
659 		// optimize query....
660 		query = optimizeQuery(query);
661 		try {
662 			Query q = manager.createQuery(query);
663 			if (startpos >= 0)
664 				q.setFirstResult(startpos);
665 			if (count > 0)
666 				q.setMaxResults(count);
667 
668 			Collection<Entity> entityList = q.getResultList();
669 
670 			if (entityList == null)
671 				return vectorResult;
672 			for (Entity aEntity : entityList) {
673 				/*
674 				 * try { manager.refresh(aEntity); } catch
675 				 * (EntityNotFoundException ex) { logger.warning(
676 				 * "[EntityServiceBean] #issue 102 - refresh() EntityNotFoundException"
677 				 * ); aEntity = null; continue; }
678 				 */
679 
680 				// implode the ItemCollection object and add it to the resultset
681 				vectorResult.add(implodeEntity(aEntity));
682 			}
683 		} catch (RuntimeException nre) {
684 			throw new InvalidAccessException(
685 					"[EntityService] Error findAllEntities: '" + query + "' ",
686 					nre);
687 		}
688 		return vectorResult;
689 
690 	}
691 
692 	/**
693 	 * The method returns the parent ItemCollection to a given child
694 	 * ItemCollection. A parent ItemCollection is referenced by a child
695 	 * ItemCollection through the property $uniqueidRef which is equals the
696 	 * parent entity's $uniqueID.
697 	 * 
698 	 * @param childentity
699 	 * @return parententity
700 	 */
701 	public ItemCollection findParentEntity(ItemCollection child)
702 			throws InvalidAccessException {
703 		String parentUniqueID = child.getItemValueString("$uniqueidref");
704 		return this.load(parentUniqueID);
705 	}
706 
707 	/**
708 	 * The method returns a collection of child ItemCollections. A child entity
709 	 * is defined by the property $uniqueidRef which points to a parent entity.
710 	 * 
711 	 * The method returns only ItemCollections which are readable by the
712 	 * CallerPrincipal. With the startPos and count parameters it is possible to
713 	 * read chunks of entities.
714 	 * 
715 	 * @see findParentEntity
716 	 * 
717 	 * @param parententity
718 	 * @param startpos
719 	 * @param count
720 	 * @return
721 	 * @throws InvalidAccessException
722 	 */
723 	public Collection<ItemCollection> findChildEntities(ItemCollection child,
724 			int start, int count) throws InvalidAccessException {
725 
726 		String parentUniqueID = child.getItemValueString("$uniqueid");
727 
728 		String sQuery = "SELECT wi FROM Entity AS wi ";
729 		sQuery += " JOIN wi.textItems as t2 ";
730 		sQuery += " WHERE t2.itemName = '$uniqueidref' and t2.itemValue = '"
731 				+ parentUniqueID + "' ";
732 
733 		return this.findAllEntities(sQuery, start, count);
734 	}
735 
736 	/**
737 	 * This method checks if the Caller Principal has read access for the
738 	 * activeEntity.
739 	 * 
740 	 * @return true if user has readaccess
741 	 */
742 	private boolean isCallerReader(Entity aEntity) {
743 
744 		List<ReadAccess> readAccessList = aEntity.getReadAccessList();
745 
746 		/**
747 		 * 1.) org.imixs.ACCESSLEVEL.NOACCESS
748 		 * 
749 		 * always = false -> no access
750 		 */
751 
752 		if (ctx.isCallerInRole(ACCESSLEVEL_NOACCESS))
753 			return false;
754 
755 		/**
756 		 * 2.) org.imixs.ACCESSLEVEL.MANAGERACCESS
757 		 * 
758 		 * always = true -> grant access.
759 		 */
760 
761 		if (ctx.isCallerInRole(ACCESSLEVEL_MANAGERACCESS))
762 			return true;
763 
764 		/**
765 		 * 2.) org.imixs.ACCESSLEVEL.EDITOR org.imixs.ACCESSLEVEL.AUTHOR
766 		 * ACCESSLEVEL.READER
767 		 * 
768 		 * check read access
769 		 */
770 		if (readAccessList == null || readAccessList.size() == 0) {
771 			// no restriction found
772 			return true;
773 		}
774 
775 		boolean notemptyfield = false;
776 
777 		// get user name list
778 		List<String> auserNameList = getUserNameList();
779 
780 		// check each read access
781 		for (ReadAccess aReadAccess : readAccessList) {
782 			if (aReadAccess != null && !"".equals(aReadAccess.getValue())) {
783 				notemptyfield = true;
784 				if (auserNameList.indexOf(aReadAccess.getValue()) > -1)
785 					return true;
786 
787 			}
788 		}
789 		if (!notemptyfield)
790 			return true;
791 
792 		return false;
793 	}
794 
795 	/**
796 	 * Verifies if the caller has write access to the current entity object.
797 	 * 
798 	 * @return
799 	 */
800 	private boolean isCallerAuthor(Entity aEntity) {
801 		List<WriteAccess> writeAccessList = aEntity.getWriteAccessList();
802 
803 		/**
804 		 * 1.) org.imixs.ACCESSLEVEL.NOACCESS allways false - now write access!
805 		 */
806 		if (ctx.isCallerInRole(ACCESSLEVEL_NOACCESS))
807 			return false;
808 
809 		/**
810 		 * 2.) org.imixs.ACCESSLEVEL.MANAGERACCESS or
811 		 * org.imixs.ACCESSLEVEL.EDITOR Always true - grant writeaccess.
812 		 */
813 		if (ctx.isCallerInRole(ACCESSLEVEL_MANAGERACCESS)
814 				|| ctx.isCallerInRole(ACCESSLEVEL_EDITORACCESS))
815 			return true;
816 
817 		/**
818 		 * 2.) org.imixs.ACCESSLEVEL.AUTHOR
819 		 * 
820 		 * check write access in detail
821 		 */
822 
823 		if (ctx.isCallerInRole(ACCESSLEVEL_AUTHORACCESS)) {
824 			if (writeAccessList == null || writeAccessList.size() == 0) {
825 				// now wirte access
826 				return false;
827 			}
828 
829 			// get user name list
830 			List<String> auserNameList = getUserNameList();
831 
832 			// check each read access
833 			for (WriteAccess aWriteAccess : writeAccessList) {
834 				if (aWriteAccess != null && !"".equals(aWriteAccess.getValue())) {
835 					if (auserNameList.indexOf(aWriteAccess.getValue()) > -1)
836 						return true; // user role known - grant access
837 				}
838 			}
839 		}
840 		return false;
841 	}
842 
843 	/**
844 	 * This method read the param USER_GROUP_LIST from the EJB ContextData. This
845 	 * context data object can provide a string array with application specific
846 	 * dynamic user groups. These groups are used to grant access to an entity
847 	 * independent from the static User-Role settings. The method returns null
848 	 * if no ContextData is set
849 	 * 
850 	 * @return - list of user group names or null if not USER_GROUP_LIST is
851 	 *         defined
852 	 */
853 	private String[] getUserGroupList() {
854 		// read dynamic user roles from the ContextData (if provided) ....
855 		String[] applicationUserGroupList = (String[]) ctx.getContextData()
856 				.get(USER_GROUP_LIST);
857 
858 		if (applicationUserGroupList != null)
859 			// trim entries....
860 			for (int i = 0; i < applicationUserGroupList.length; i++) {
861 				applicationUserGroupList[i] = applicationUserGroupList[i]
862 						.trim();
863 			}
864 
865 		return applicationUserGroupList;
866 	}
867 
868 	/**
869 	 * This method updates the internal WriteAccessList. Therefore the method
870 	 * verifies if the itemCollection contains WriteAccess properties. The
871 	 * default property name is '$writeaccess'. Additional property names can be
872 	 * provided by the resource "WRITE_ACCESS_FIELDS" through the deployment
873 	 * descriptor. Empty values will be ignored.
874 	 * <p>
875 	 * The Values of the corresponding Items in the ItemCollection will not be
876 	 * removed by this method because access values could be assigned to
877 	 * different properties.
878 	 * 
879 	 * @return the new itemCollection without writeaccess properties
880 	 */
881 	private void updateWriteAccessList(ItemCollection itemCol, Entity aEntity) {
882 		List<String> vAccessFieldList = new ArrayList<String>();
883 
884 		// remove current WriteAccess List
885 
886 		for (WriteAccess aItem : aEntity.getWriteAccessList())
887 			manager.remove(aItem);
888 		aEntity.getWriteAccessList().clear();
889 
890 		// create fieldname list and add the default fieldname
891 		vAccessFieldList.add("$writeAccess");
892 
893 		// test if the Resource WRITE_ACCESS_FIELDS is provided
894 		if (writeAccessFields != null && !"".equals(writeAccessFields)) {
895 			String[] stringArrayList = writeAccessFields.split(",");
896 			for (String aentry : stringArrayList) {
897 				vAccessFieldList.add(aentry);
898 			}
899 		}
900 
901 		// process all attributes
902 		for (String aentry : vAccessFieldList) {
903 			// add values. But do not add empty string entries here!
904 			List vEntryList = itemCol.getItemValue(aentry);
905 			for (Object avalue : vEntryList) {
906 				if (avalue != null && !"".equals(avalue.toString())) {
907 					// now persist and add the new Item
908 					WriteAccess newItem = new WriteAccess(avalue.toString());
909 					manager.persist(newItem);
910 					aEntity.getWriteAccessList().add(newItem);
911 				}
912 			}
913 		}
914 	}
915 
916 	/**
917 	 * This method updates the internal ReadAccessList. Therefore the method
918 	 * verifies if the itemCollection contains ReadAccess properties. The
919 	 * default property name is '$readaccess'. Additional property names can be
920 	 * provided by the resource "READ_ACCESS_FIELDS" through the deployment
921 	 * descriptor. Empty values will be ignored!
922 	 * <p>
923 	 * The Values of the corresponding Items in the ItemCollection will not be
924 	 * removed by this method because access values could be assigned to
925 	 * different properties.
926 	 * 
927 	 * @return the new itemCollection without writeaccess properties
928 	 */
929 	private void updateReadAccessList(ItemCollection itemCol, Entity aEntity) {
930 		List<String> vAccessFieldList = new ArrayList<String>();
931 
932 		// remove current ReadAccess List
933 		for (ReadAccess aItem : aEntity.getReadAccessList())
934 			manager.remove(aItem);
935 		aEntity.getReadAccessList().clear();
936 
937 		// add default fieldname
938 		vAccessFieldList.add("$readAccess");
939 
940 		// test if the Resource WRITE_ACCESS_FIELDS is provided
941 		if (readAccessFields != null && !"".equals(readAccessFields)) {
942 			String[] stringArrayList = readAccessFields.split(",");
943 			for (String aentry : stringArrayList) {
944 				vAccessFieldList.add(aentry);
945 			}
946 		}
947 
948 		// process all attributes
949 		for (String aentry : vAccessFieldList) {
950 			// add values. But do not add empty string entries here!
951 			List vEntryList = itemCol.getItemValue(aentry);
952 			for (Object avalue : vEntryList) {
953 				if (avalue != null && !"".equals(avalue.toString())) {
954 					// now persist and add the new Item
955 					ReadAccess newItem = new ReadAccess(avalue.toString());
956 					manager.persist(newItem);
957 					aEntity.getReadAccessList().add(newItem);
958 				}
959 			}
960 		}
961 	}
962 
963 	/**
964 	 * Explodes an ItemCollection into a Entity with its index fields. This
965 	 * method extracts all items with corresponding Indices into the subtables.
966 	 * Existing Entries will be removed.
967 	 * 
968 	 * This method extracts an single property of the ItemCollection (data
969 	 * object of the activeEntity) into the corresponding index properties
970 	 * (TextItem, IntegerItem DoubleItem, CalendarItem).
971 	 * <p>
972 	 * The ItemName will be transformed to lowercase!
973 	 * <p>
974 	 * If an instance of a Date Object is provided in the ItemCollection the
975 	 * values will be converted from the Date object into a Calendar Object.
976 	 * This is because the ItemCollection works typical with Date Objects
977 	 * instead of Calendar Objects.
978 	 * <p>
979 	 * After the method has moved an attribute with its values to the
980 	 * corresponding Index Property the method removes the attribute form the
981 	 * itemCollection.
982 	 * <p>
983 	 * If a value of a property did not match the indexProperty Type a
984 	 * ClassCastExcepiton will be thrown. This is the situation if one or more
985 	 * single values did
986 	 * 
987 	 * not match the propertyIndex Type. The method prints a warning to the log
988 	 * file but did continue proceeding. This results into a situation where
989 	 * invalid values will be stored in the ItemCollections value list. <br>
990 	 * In this situation the entity exists in a invalid structure and did not
991 	 * match the data model
992 	 * 
993 	 */
994 	private void explodeEntity(ItemCollection itemCol, Entity aEntity,
995 			Collection<EntityIndex> entityIndexCache) {
996 
997 		// in case of optimistic locking is disabled we remove $version 
998 		if (disbaleOptimisticLocking) {
999 			itemCol.removeItem("$Version");
1000 		} else if (itemCol.hasItem("$Version")) {
1001 			// if $version is provided we update the version number of the entity!
1002 			aEntity.setVersion(itemCol.getItemValueInteger("$Version"));
1003 		}
1004 
1005 		// remove current TextItem List
1006 		for (TextItem aItem : aEntity.getTextItems())
1007 			manager.remove(aItem);
1008 		aEntity.getTextItems().clear();
1009 
1010 		// remove current CalendarItem List
1011 		for (CalendarItem aItem : aEntity.getCalendarItems())
1012 			manager.remove(aItem);
1013 		aEntity.getCalendarItems().clear();
1014 
1015 		// remove current IntegerItem List
1016 		for (IntegerItem aItem : aEntity.getIntegerItems())
1017 			manager.remove(aItem);
1018 		aEntity.getIntegerItems().clear();
1019 
1020 		// remove current IntegerItem List
1021 		for (DoubleItem aItem : aEntity.getDoubleItems())
1022 			manager.remove(aItem);
1023 		aEntity.getDoubleItems().clear();
1024 
1025 		// for each index update the
1026 		for (EntityIndex index : entityIndexCache) {
1027 			if (index.getTyp() == EntityIndex.TYP_TEXT) {
1028 				// get the value list fom the itemcolection
1029 				List valueList = itemCol.getItemValue(index.getName());
1030 				for (Object asingleValue : valueList) {
1031 					TextItem newItem = new TextItem(index.getName(),
1032 							asingleValue.toString());
1033 					manager.persist(newItem);
1034 					aEntity.getTextItems().add(newItem);
1035 				}
1036 				// now remove the attribute from ItemCollection
1037 				itemCol.removeItem(index.getName());
1038 				continue;
1039 			}
1040 			if (index.getTyp() == EntityIndex.TYP_INT) {
1041 				// get the value list fom the itemcolection
1042 				List valueList = itemCol.getItemValue(index.getName());
1043 				// handle classCastExcpetions...
1044 				boolean bClassCastException = false;
1045 				Vector vInvalidValues = new Vector();
1046 				for (Object asingleValue : valueList) {
1047 					try {
1048 						IntegerItem newItem = new IntegerItem(index.getName(),
1049 								(Integer) asingleValue);
1050 						manager.persist(newItem);
1051 						aEntity.getIntegerItems().add(newItem);
1052 					} catch (ClassCastException cce) {
1053 						bClassCastException = true;
1054 						logger.warning("explodeEntity - " + index.getName()
1055 								+ " TYP_INT: " + cce.getMessage());
1056 						vInvalidValues.add(asingleValue);
1057 					}
1058 				}
1059 				// now remove the attribute from ItemCollection
1060 				if (!bClassCastException)
1061 					itemCol.removeItem(index.getName());
1062 				else
1063 					itemCol.replaceItemValue(index.getName(), vInvalidValues);
1064 				continue;
1065 			}
1066 
1067 			if (index.getTyp() == EntityIndex.TYP_DOUBLE) {
1068 				// get the value list fom the itemcolection
1069 				List valueList = itemCol.getItemValue(index.getName());
1070 				// handle classCastExcpetions...
1071 				boolean bClassCastException = false;
1072 				List vInvalidValues = new Vector();
1073 				for (Object asingleValue : valueList) {
1074 					try {
1075 						DoubleItem newItem = new DoubleItem(index.getName(),
1076 								(Double) asingleValue);
1077 						manager.persist(newItem);
1078 						aEntity.getDoubleItems().add(newItem);
1079 					} catch (ClassCastException cce) {
1080 						bClassCastException = true;
1081 						logger.warning("explodeEntity - " + index.getName()
1082 								+ " TYP_DOUBLE: " + cce.getMessage());
1083 						vInvalidValues.add(asingleValue);
1084 					}
1085 				}
1086 				// now remove the attribute from ItemCollection
1087 				if (!bClassCastException)
1088 					itemCol.removeItem(index.getName());
1089 				else
1090 					itemCol.replaceItemValue(index.getName(), vInvalidValues);
1091 				continue;
1092 			}
1093 
1094 			if (index.getTyp() == EntityIndex.TYP_CALENDAR) {
1095 				// get the value list fom the itemcolection
1096 				List valueList = itemCol.getItemValue(index.getName());
1097 				// handle classCastExcpetions...
1098 				boolean bClassCastException = false;
1099 				Vector vInvalidValues = new Vector();
1100 				for (Object asingleValue : valueList) {
1101 					try {
1102 						if (asingleValue instanceof java.util.Date) {
1103 							Calendar cal = Calendar.getInstance();
1104 							cal.setTime((java.util.Date) asingleValue);
1105 							asingleValue = cal;
1106 						}
1107 
1108 						CalendarItem newItem = new CalendarItem(
1109 								index.getName(), (Calendar) asingleValue);
1110 						manager.persist(newItem);
1111 						aEntity.getCalendarItems().add(newItem);
1112 					} catch (ClassCastException cce) {
1113 						bClassCastException = true;
1114 						logger.warning("explodeEntity - " + index.getName()
1115 								+ " TYP_CALENDAR: " + cce.getMessage());
1116 						vInvalidValues.add(asingleValue);
1117 					}
1118 				}
1119 				// now remove the attribute from ItemCollection
1120 				if (!bClassCastException)
1121 					itemCol.removeItem(index.getName());
1122 				else
1123 					itemCol.replaceItemValue(index.getName(), vInvalidValues);
1124 				continue;
1125 			}
1126 
1127 			logger.warning(" explodeEntity - " + index.getName()
1128 					+ " Indextype:" + index.getTyp() + " unknown!");
1129 
1130 		}
1131 
1132 	}
1133 
1134 	/**
1135 	 * This method returns a new Instance of an ItemCollection based of an given
1136 	 * Entity object. The method constructs a new ItemCollection instance
1137 	 * containing all values of the Entity data. Properties which where
1138 	 * separated out into the index properties (TextItems, IntegerItems,
1139 	 * DoubleItems, CalendarItems) will be moved back into the ItemCollection
1140 	 * Object returned by this method. So the returned ItemCollection will
1141 	 * contain all attributes managed currently by the activeEntity.
1142 	 * <p>
1143 	 * The method takes care about the fact that the values of the activeEntiy
1144 	 * are typical managed by the PersistenceManger. For this reason the method
1145 	 * did not copy the Vector objects into the new detached Instance of the new
1146 	 * ItemCollection but copies only the values of each vector. This mechanism
1147 	 * guarantees that the reconstruction of indexProperties did not affect the
1148 	 * managed activeEntity and its binded objects. So it is not necessary that
1149 	 * the activeEntity was detached before this method is called as the method
1150 	 * will not! modify property values which would be written back to the
1151 	 * database.
1152 	 * <p>
1153 	 * Calendar Objects will be converted into Date Objects. ItemCollection
1154 	 * works typical with Date Objects instead of Calendar Objects.
1155 	 * <p>
1156 	 * The method verifies if the callerPricipal has author access and adds the
1157 	 * attribute $isAuthor into the ItemCollection to indicate the author access
1158 	 * of this entity for the current user.
1159 	 * 
1160 	 * <p>
1161 	 * Note: $readAccess and $writeAccess fields are not removed during save. so
1162 	 * we did not care about those values
1163 	 * 
1164 	 * @see explodeEntity()
1165 	 * @return
1166 	 */
1167 	@SuppressWarnings("unchecked")
1168 	private ItemCollection implodeEntity(Entity aEntity) {
1169 		// create new empty ItemCollection
1170 		ItemCollection itemCollection = new ItemCollection();
1171 
1172 		// verify author access and add property '$isauthor'
1173 		try {
1174 			itemCollection.replaceItemValue("$isauthor",
1175 					isCallerAuthor(aEntity));
1176 		} catch (Exception e1) {
1177 		}
1178 
1179 		/*
1180 		 * Now we first copy for each IndexProperty the values into our new
1181 		 * ItemColleciton Object.
1182 		 */
1183 
1184 		// so now copy all separated TextItems
1185 		for (TextItem textItem : aEntity.getTextItems()) {
1186 			try {
1187 				List vValue = itemCollection.getItemValue(textItem.itemName);
1188 				vValue.add(textItem.itemValue);
1189 				itemCollection.replaceItemValue(textItem.itemName, vValue);
1190 			} catch (Exception e) {
1191 				logger.warning("[EntityService] could not implode ItemValue: "
1192 						+ textItem.itemName);
1193 			}
1194 		}
1195 
1196 		// copy all separated IntegerItems
1197 		for (IntegerItem ii : aEntity.getIntegerItems()) {
1198 			try {
1199 				List vValue = itemCollection.getItemValue(ii.itemName);
1200 				vValue.add(ii.itemValue);
1201 				itemCollection.replaceItemValue(ii.itemName, vValue);
1202 			} catch (Exception e) {
1203 				logger.warning("[EntityService] could not implode ItemValue: "
1204 						+ ii.itemName);
1205 			}
1206 		}
1207 
1208 		// copy all separated DoubleItems
1209 		for (DoubleItem di : aEntity.getDoubleItems()) {
1210 			try {
1211 				List vValue = itemCollection.getItemValue(di.itemName);
1212 				vValue.add(di.itemValue);
1213 				itemCollection.replaceItemValue(di.itemName, vValue);
1214 			} catch (Exception e) {
1215 				logger.warning("[EntityService] could not implode ItemValue: "
1216 						+ di.itemName);
1217 			}
1218 		}
1219 
1220 		// copy all separated CalendarItems
1221 		for (CalendarItem ci : aEntity.getCalendarItems()) {
1222 			try {
1223 				List vValue = itemCollection.getItemValue(ci.itemName);
1224 				// Calendar Objects will be converted into Date Objects
1225 				vValue.add(ci.itemValue.getTime());
1226 				itemCollection.replaceItemValue(ci.itemName, vValue);
1227 			} catch (Exception e) {
1228 				logger.warning("[EntityService] could not implode ItemValue: "
1229 						+ ci.itemName);
1230 			}
1231 		}
1232 
1233 		/*
1234 		 * After we added all indexProperties to our empty ItemCollection we can
1235 		 * now add the rest of the properties contained in the EntityData
1236 		 * Object. But we need to make sure that we do not overwrite an new
1237 		 * created index property. So we do not simply call the putAll() method.
1238 		 * But we verify each property name and value. If its new than we will
1239 		 * copy the values - again not the Vector! as we want to avoid to
1240 		 * manipulate or return managed objects. So we do create new Vector()
1241 		 * instances for each property
1242 		 */
1243 
1244 		// ! do not use :
1245 		// detachedItemCollection.getAllItems().putAll(activeEntity.getData().getItemCollection().getAllItems());
1246 		Map activeMap = aEntity.getData();
1247 		if (activeMap != null) {
1248 			// iterate over all properties
1249 			Iterator iter = activeMap.entrySet().iterator();
1250 			while (iter.hasNext()) {
1251 				Map.Entry mapEntry = (Map.Entry) iter.next();
1252 				// extract name and value
1253 				String activePropertyName = mapEntry.getKey().toString();
1254 				if (!itemCollection.hasItem(activePropertyName)) {
1255 					// iitemValuef we did not have created this property from
1256 					// the
1257 					// indexProperties before, so we add a new property....
1258 					Vector activePropertyValue = (Vector) mapEntry.getValue();
1259 					// create new Vector
1260 					Vector detachePropertyValue = new Vector();
1261 					// copy objects
1262 					detachePropertyValue.addAll(activePropertyValue);
1263 					// add property
1264 					try {
1265 						itemCollection.replaceItemValue(activePropertyName,
1266 								detachePropertyValue);
1267 					} catch (Exception e) {
1268 						// unexpected exception - but we will continue work
1269 						logger.warning("[EntityService] Warning implodeEntity - "
1270 								+ e.getMessage());
1271 					}
1272 				}
1273 				// now we are sure that we have a new instance of an vector
1274 				// for each property
1275 			}
1276 		}
1277 
1278 		// if disable Optimistic Locking is TRUE we do not add the version
1279 		// number
1280 		if (!disbaleOptimisticLocking) {
1281 			itemCollection.replaceItemValue("$Version", aEntity.getVersion());
1282 		}
1283 
1284 		return itemCollection;
1285 	}
1286 
1287 	/**
1288 	 * This method is used to optimize a query by adding a where clause which
1289 	 * ensures that the query will only include entities accessible by the
1290 	 * current user. For this reason the method adds a clause which test the
1291 	 * readAccess property for predefined roles and usernames.
1292 	 * <p>
1293 	 * This method is most important to secure each findAllEntiteis method call.
1294 	 * <p>
1295 	 * Example
1296 	 * <p>
1297 	 * Here are some Examples:
1298 	 * <ul>
1299 	 * <li>SELECT entity FROM Entity entity
1300 	 * <li>convert into
1301 	 * <li>SELECT entity FROM Entity entity, entity.readAccessList access WHERE
1302 	 * access.value IN ('xxx')
1303 	 * </ul>
1304 	 * 
1305 	 * <ul>
1306 	 * <li>SELECT entity FROM Entity entity WHERE entity.type='workitem'
1307 	 * <li>convert into
1308 	 * <li>SELECT entity FROM Entity entity, entity.readAccessList access WHERE
1309 	 * access.value IN ('rsoika') AND entity.type='workitem'
1310 	 * </ul>
1311 	 * <p>
1312 	 * The Method also verifies if a DISTINCT clause is used in the Query. If
1313 	 * not the method will add a distinct clause to avoid duplicates in the
1314 	 * returned result set. duplicates can be returned by complex queries with
1315 	 * multiple joins.
1316 	 * 
1317 	 * @param aQuery
1318 	 * @return
1319 	 * @throws InvalidAccessException
1320 	 * 
1321 	 */
1322 	private String optimizeQuery(String aQuery) throws InvalidAccessException {
1323 		// String nameList = "";
1324 		StringBuffer nameListBuf = new StringBuffer();
1325 		String nameList = "";
1326 		aQuery = aQuery.trim();
1327 		StringTokenizer st = new StringTokenizer(aQuery);
1328 		// find identifier for Entity
1329 		if (st.countTokens() < 5)
1330 			throw new InvalidAccessException(
1331 					"[EntityService] Invalid query format: " + aQuery);
1332 
1333 		// test if DISTINCT clause is included
1334 		st.nextToken();
1335 		String sDistinct = st.nextToken();
1336 		if (!"distinct".equals(sDistinct.toLowerCase().trim())) {
1337 			// add distinct.
1338 			int iDisPos = aQuery.toLowerCase().indexOf("select") + 6;
1339 			aQuery = aQuery.substring(0, iDisPos) + " DISTINCT"
1340 					+ aQuery.substring(iDisPos);
1341 		}
1342 
1343 		// don't optimize for managers...
1344 		if (ctx.isCallerInRole(ACCESSLEVEL_MANAGERACCESS))
1345 			return aQuery;
1346 
1347 		// now construct user role list
1348 		List<String> auserNameList = getUserNameList();
1349 		for (String auserName : auserNameList) {
1350 			nameListBuf.append(",'" + auserName + "'");
1351 		}
1352 		// remove first ,
1353 		nameListBuf.deleteCharAt(0);
1354 
1355 		nameList = nameListBuf.toString();
1356 
1357 		logger.fine("Optimized NameList = " + nameList);
1358 
1359 		// now select identifier - this is the last word before a 'WHERE',
1360 		// 'ORDER BY' or 'JOIN'
1361 		int iPos = 0;
1362 		// we begin with join
1363 		iPos = aQuery.toLowerCase().indexOf("join");
1364 		if (iPos == -1)
1365 			// test for where
1366 			iPos = aQuery.toLowerCase().indexOf("where");
1367 		if (iPos == -1)
1368 			// and end to test for order by clause
1369 			iPos = aQuery.toLowerCase().indexOf("order by");
1370 
1371 		String firstPart;
1372 		if (iPos == -1)
1373 			firstPart = aQuery;
1374 		else
1375 			firstPart = aQuery.substring(0, iPos - 1);
1376 
1377 		firstPart = firstPart.trim();
1378 
1379 		iPos = firstPart.length();
1380 		// go back to first ' '
1381 		String identifier = null;
1382 		identifier = firstPart.substring(firstPart.lastIndexOf(' ')).trim();
1383 
1384 		// test for ,
1385 		if (identifier.endsWith(","))
1386 			identifier = identifier.substring(0, identifier.length() - 1);
1387 
1388 		// add access JOIN (should be a unique token name)
1389 		String aNewQuery = aQuery.substring(0, iPos);
1390 		aNewQuery += " LEFT JOIN " + identifier + ".readAccessList access807 ";
1391 		aNewQuery += aQuery.substring(iPos);
1392 
1393 		aQuery = aNewQuery.trim();
1394 		// test if WHERE clause is available
1395 		int iWherePos = aQuery.toLowerCase().indexOf("where");
1396 		if (iWherePos > -1) {
1397 			// insert access check
1398 			aNewQuery = aQuery.substring(0, iWherePos + 5)
1399 					+ " (access807.value IS NULL OR access807.value IN ("
1400 					+ nameList + ")) AND " + aQuery.substring(iWherePos + 6);
1401 			aQuery = aNewQuery;
1402 
1403 		} else {
1404 			// no WHERE clause - so add a new one
1405 			int iOrderPos = aQuery.toLowerCase().indexOf("order by");
1406 			if (iOrderPos > -1)
1407 				aNewQuery = aQuery.substring(0, iOrderPos - 1)
1408 						+ " WHERE (access807.value IS NULL OR access807.value IN ("
1409 						+ nameList + ")) " + aQuery.substring(iOrderPos);
1410 			else
1411 				aNewQuery = aQuery
1412 						+ " WHERE (access807.value IS NULL OR access807.value IN("
1413 						+ nameList + ")) ";
1414 
1415 			aQuery = aNewQuery;
1416 		}
1417 
1418 		logger.fine("Optimized Query=" + aQuery);
1419 		return aQuery;
1420 	}
1421 
1422 	/**
1423 	 * This Method updates the index properties from a collection of entities.
1424 	 * The Method first implodes the Entity to get all external properties and
1425 	 * than calls an explode to recreate the index properties.
1426 	 * 
1427 	 * 
1428 	 * Method can be optimized
1429 	 * 
1430 	 * 
1431 	 * @see issue #62
1432 	 */
1433 	private void updateAllEntityIndexFields(Collection<Entity> entityList,
1434 			String newIndexField) {
1435 		long count = 0;
1436 
1437 		// get a List of all existing Indices
1438 		Collection<EntityIndex> entityIndexCache = getIndices();
1439 
1440 		Iterator<Entity> iter = entityList.iterator();
1441 		while (iter.hasNext()) {
1442 			Entity activeEntity = iter.next();
1443 			ItemCollection slimItemcol = new ItemCollection(
1444 					activeEntity.getData());
1445 			// test if activeEntity is affected from this index
1446 			if (newIndexField == null || slimItemcol.hasItem(newIndexField)) {
1447 
1448 				try {
1449 					/*
1450 					 * try { manager.refresh(activeEntity); } catch
1451 					 * (EntityNotFoundException ex) { logger.warning(
1452 					 * "[EntityServiceBean] #issue 102 - refresh() EntityNotFoundException"
1453 					 * ); activeEntity = null; }
1454 					 */
1455 
1456 					// implode each Entity into its ItemCollection
1457 					ItemCollection itemcol = implodeEntity(activeEntity);
1458 
1459 					ItemCollection slimItemCollection = new ItemCollection(
1460 							itemcol.getAllItems());
1461 
1462 					// update read- and writeAccess List
1463 					updateReadAccessList(slimItemCollection, activeEntity);
1464 					updateWriteAccessList(slimItemCollection, activeEntity);
1465 
1466 					explodeEntity(slimItemCollection, activeEntity,
1467 							entityIndexCache);
1468 
1469 					// finally update the data field
1470 					activeEntity.setData(slimItemCollection.getAllItems());
1471 
1472 				} catch (Exception merex) {
1473 					logger.info("[EntityServiceBean] Error updateAllEntityIndexFields for Entity : "
1474 							+ activeEntity.getId());
1475 					merex.printStackTrace();
1476 
1477 				}
1478 				count++;
1479 			}
1480 		}
1481 		logger.info("[EntityServiceBean] " + count + " effective updates");
1482 	}
1483 
1484 }