Java tutorial
/* * Swordess-ldap, an Object-Directory Mapping tool. * * Copyright (c) 2013, 2013 Liu Xingyu. * * Swordess-ldap is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * Swordess-ldap is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Swordess-ldap. If not, see <http://www.gnu.org/licenses/>. */ package org.swordess.ldap.odm.core; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.naming.NameNotFoundException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; import javax.naming.directory.ModificationItem; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.InitialLdapContext; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.swordess.ldap.Session; import org.swordess.ldap.SessionException; import org.swordess.ldap.odm.Distinguishable; import org.swordess.ldap.odm.core.EntityProxyFactory.SetterInterceptor; import org.swordess.ldap.odm.metadata.entity.EntityMetaData; import org.swordess.ldap.odm.metadata.entity.EntityPropertyMetaData; import org.swordess.ldap.odm.metadata.indirections.IndirectionsMetaData; import org.swordess.ldap.odm.metadata.indirections.OneMetaData; import org.swordess.ldap.odm.metadata.indirections.TheOtherMetaData; import org.swordess.ldap.util.AttrUtils; import org.swordess.ldap.util.CollectionUtils; import org.swordess.ldap.util.Evaluator; import org.swordess.ldap.util.LogUtils; import org.swordess.ldap.util.ModUtils; public class SessionImpl implements Session { private static final Log LOG = LogFactory.getLog(SessionImpl.class); private Map<String, Object> sessionCache = new HashMap<String, Object>(); private final AbstractThreadLocalSessionFactory sessionFactory; private final InitialLdapContext ctx; private final boolean bindToThreadLocal; SessionImpl(AbstractThreadLocalSessionFactory sessionFactory, InitialLdapContext ctx, boolean bindToThreadLocal) { this.sessionFactory = sessionFactory; this.ctx = ctx; this.bindToThreadLocal = bindToThreadLocal; } @Override public void create(Object obj) { if (null == obj) { return; } if (obj instanceof Persistent) { update(obj); return; } Object idValue = EntityMetaData.get(obj.getClass()).getIdProperty().getter().get(obj); if (null == idValue) { throw new SessionException("Unable to persist an object which has no id: " + obj); } String dn = DnHelper.build(idValue.toString(), obj.getClass()); LogUtils.debug(LOG, "create dn=" + dn); try { ctx.bind(dn, null, fromTransientToAttributes(obj)); /* * We didn't do an extra lookup invocation to put it into the cache, * as we have no idea whether the client code need the persisted * entity or not. Besides, if the answer is no, we slow down the * speed. If yes, the client code still hold the original reference * of the transient object and we would rather the client code to do * the extra lookup by itself. */ } catch (NamingException e) { throw new SessionException(e.getMessage(), e); } } @Override public void createIndirections(Object indirections) { if (null == indirections) { return; } if (indirections instanceof Persistent) { updateIndirections(indirections); return; } IndirectionsMetaData metaData = IndirectionsMetaData.get(indirections.getClass()); String one = metaData.getOne().getter().get(indirections); List<String> theOther = metaData.getTheOther().getter().get(indirections); if (StringUtils.isEmpty(one) || CollectionUtils.isEmpty(theOther)) { LogUtils.debug(LOG, "either one or theOthere is empty, do nothing"); return; } connectIndirections(metaData, one, theOther); } @Override public void update(Object entity) { if (null == entity) { return; } if (!(entity instanceof Persistent)) { create(entity); return; } String dn = DnHelper.build(entity); List<ModificationItem> mods = fromEntityToModificationItems(entity); if (mods.isEmpty()) { LogUtils.debug(LOG, "no changes found when updating dn=" + dn + ", do nothing"); return; } LogUtils.debug(LOG, "update dn=" + dn); try { ctx.modifyAttributes(dn, mods.toArray(new ModificationItem[0])); /* * All current modifications are cleared, so could we continue * to use this entity? The answer is no! * * For simple string properties, this works fine. But for * multiple values we need to do following things before reusing * this entity: * 1. clear changes of all the modified MonitoredList * 2. turn normal List into MonitoredList */ EntityProxyFactory.getModifiedPropNames(entity).clear(); for (EntityPropertyMetaData propMetaData : EntityMetaData.get(ClassHelper.actualClass(entity))) { if (propMetaData.isReadonly() || !propMetaData.isMultiple()) { continue; } Object propValue = propMetaData.getter().get(entity); if (null == propValue) { continue; } List propValues = (List) propValue; if (propValues instanceof MoniteredList) { // clear changes of all the modifed MoniteredList ((MoniteredList) propValues).clearChanges(); } else { // turn normal List into MoniteredList propMetaData.setter().set(entity, new MoniteredList(propValues)); } } /* * Now we have no need to remove the entity from the cache. And its * possible to continuous use of the entity. */ } catch (NamingException e) { throw new SessionException(e.getMessage(), e); } } @Override public void updateIndirections(Object indirections) { if (null == indirections) { return; } if (!(indirections instanceof Persistent)) { createIndirections(indirections); return; } IndirectionsMetaData metaData = IndirectionsMetaData.get(ClassHelper.actualClass(indirections)); String one = metaData.getOne().getter().get(indirections); List<String> theOther = metaData.getTheOther().getter().get(indirections); String originalOne = IndirectionsProxyFactory.getOriginalOne(indirections); List<String> originalTheOther = IndirectionsProxyFactory.getOriginalTheOther(indirections); if (StringUtils.isEmpty(one) || CollectionUtils.isEmpty(theOther)) { if (StringUtils.isEmpty(one) && CollectionUtils.isEmpty(theOther)) { LogUtils.debug(LOG, "both one and theOther of " + indirections + " are empty"); } disconnectIndirections(metaData, originalOne, originalTheOther); } else { if (one.equals(originalOne)) { List<String> removed, added; if (theOther instanceof MoniteredList) { MoniteredList<String> monitered = (MoniteredList<String>) theOther; removed = monitered.getRemovedElements(); added = monitered.getAddedElements(); } else { removed = new ArrayList<String>(originalTheOther); removed.removeAll(theOther); added = new ArrayList<String>(theOther); added.removeAll(originalTheOther); } if (!CollectionUtils.isEmpty(removed)) { disconnectIndirections(metaData, one, removed); } if (!CollectionUtils.isEmpty(added)) { connectIndirections(metaData, one, added); } } else { disconnectIndirections(metaData, originalOne, originalTheOther); connectIndirections(metaData, one, theOther); } IndirectionsProxyFactory.refreshOriginals(indirections); if (null != theOther) { if (theOther instanceof MoniteredList) { ((MoniteredList) theOther).clearChanges(); } else { metaData.getTheOther().setter().set(indirections, new MoniteredList(theOther)); } } } } @Override public void delete(String dn) { if (StringUtils.isEmpty(dn)) { return; } LogUtils.debug(LOG, "delete dn=" + dn); try { ctx.unbind(dn); sessionCache.remove(dn); } catch (NameNotFoundException ignore) { LogUtils.debug(LOG, "Name not found: " + dn); } catch (NamingException e) { throw new SessionException(e.getMessage(), e); } } @Override public void delete(Object entity) { if (null == entity) { return; } if (!(entity instanceof Persistent)) { LogUtils.debug(LOG, entity + " is not persistent, do nothing"); return; } String dn = DnHelper.build(entity); LogUtils.debug(LOG, "delete dn=" + dn); try { ctx.unbind(dn); sessionCache.remove(dn); } catch (NamingException e) { throw new SessionException(e.getMessage(), e); } } @Override public void deleteIndirections(Object indirections) { if (null == indirections) { return; } IndirectionsMetaData metaData = IndirectionsMetaData.get(ClassHelper.actualClass(indirections)); String one; List<String> theOther; if (indirections instanceof Persistent) { one = IndirectionsProxyFactory.getOriginalOne(indirections); theOther = IndirectionsProxyFactory.getOriginalTheOther(indirections); } else { one = metaData.getOne().getter().get(indirections); theOther = metaData.getTheOther().getter().get(indirections); } disconnectIndirections(metaData, one, theOther); } @SuppressWarnings("unchecked") @Override public <T> T read(Class<T> clazz, String dn) { if (null == dn) { return null; } LogUtils.debug(LOG, "read " + clazz.getName() + " with dn=" + dn); if (sessionCache.containsKey(dn)) { LogUtils.info(LOG, "cache hit " + dn); return (T) sessionCache.get(dn); } try { Attributes allDefinedAttrs = ctx.getAttributes(dn, EntityMetaData.getDefinedAttrNames(clazz)); T entity = fromAttributesToEntity(clazz, allDefinedAttrs); sessionCache.put(dn, entity); return entity; } catch (NameNotFoundException e) { return null; } catch (NamingException e) { throw new SessionException(e.getMessage(), e); } } @Override public Map<String, Object> read(Class<?> clazz, String dn, String[] returningAttrs) { if (null == dn) { return null; } LogUtils.debug(LOG, String.format("read %s with dn=%s, returningAttrs=%s", clazz.getName(), dn, Arrays.toString(returningAttrs))); // fetch the information from session cache first to decrease one possible lookup operation if (sessionCache.containsKey(dn)) { Object entity = sessionCache.get(dn); Map<String, Object> retVal = new HashMap<String, Object>(); EntityMetaData metaData = EntityMetaData.get(clazz); for (String returningAttr : returningAttrs) { EntityPropertyMetaData propMetaData = metaData.getProperty(returningAttr); if (null == propMetaData) { continue; } Object propValue = propMetaData.getter().get(entity); if (null == propValue) { continue; } if (!propMetaData.isMultiple()) { retVal.put(returningAttr, propValue); } else { List propValues = (List) propValue; if (!propValues.isEmpty()) { if (!propMetaData.isReference()) { retVal.put(returningAttr, propValues); } else { List simpleValues = new ArrayList(); for (Object referenceEntity : propValues) { simpleValues.add(DnHelper.build(referenceEntity)); } retVal.put(returningAttr, simpleValues); } } } } return retVal; } else { try { return fromAttributesToMap(clazz, ctx.getAttributes(dn, returningAttrs)); } catch (NamingException e) { throw new SessionException(e.getMessage(), e); } } } @Override public <T> List<T> search(Class<T> clazz, String filter) { if (null == filter) { return null; } LogUtils.debug(LOG, "search " + clazz.getName() + " with filter=" + filter); SearchControls ctrl = new SearchControls(); ctrl.setSearchScope(SearchControls.SUBTREE_SCOPE); ctrl.setReturningAttributes(EntityMetaData.getDefinedAttrNames(clazz)); List<T> retVal = new ArrayList<T>(); try { NamingEnumeration<SearchResult> results = ctx.search(EntityMetaData.get(clazz).context(), filter, ctrl); while (results.hasMore()) { try { SearchResult result = results.next(); T entity = null; if (sessionCache.containsKey(result.getNameInNamespace())) { // guarantee the reference integrity for one search result entity = (T) sessionCache.get(result.getNameInNamespace()); } else { entity = fromAttributesToEntity(clazz, result.getAttributes()); sessionCache.put(result.getNameInNamespace(), entity); } retVal.add(entity); } catch (NamingException e) { LogUtils.error(LOG, "Unable to construct the entity", e); } } } catch (NamingException e) { throw new SessionException(e.getMessage(), e); } return retVal; } @Override public List<Map<String, Object>> search(Class<?> clazz, String filter, String[] returningAttrs) { if (null == filter) { return null; } LogUtils.debug(LOG, String.format("search %s with filter=%s, returningAttrs=%s", clazz.getName(), filter, Arrays.toString(returningAttrs))); SearchControls ctrl = new SearchControls(); ctrl.setSearchScope(SearchControls.SUBTREE_SCOPE); ctrl.setReturningAttributes(returningAttrs); try { List<Map<String, Object>> retVal = new ArrayList<Map<String, Object>>(); NamingEnumeration<SearchResult> results = ctx.search(EntityMetaData.get(clazz).context(), filter, ctrl); while (results.hasMore()) { try { SearchResult result = results.next(); retVal.add(fromAttributesToMap(clazz, result.getAttributes())); } catch (NamingException e) { LogUtils.error(LOG, "Unable to construct the map", e); } } return retVal; } catch (NamingException e) { throw new SessionException(e.getMessage(), e); } } @Override public List<Map<String, Object>> search(String context, String filter, String[] returningAttrs) { if (null == filter) { return null; } LogUtils.debug(LOG, String.format("search %s with filter=%s, returningAttrs=%s", context, filter, Arrays.toString(returningAttrs))); SearchControls ctrl = new SearchControls(); ctrl.setSearchScope(SearchControls.SUBTREE_SCOPE); ctrl.setReturningAttributes(returningAttrs); try { List<Map<String, Object>> retVal = new ArrayList<Map<String, Object>>(); NamingEnumeration<SearchResult> results = ctx.search(context, filter, ctrl); while (results.hasMore()) { try { SearchResult result = results.next(); retVal.add(fromAttributesToMap(result.getAttributes())); } catch (NamingException e) { LogUtils.error(LOG, "Unable to construct the map", e); } } return retVal; } catch (NamingException e) { throw new SessionException(e.getMessage(), e); } } public List<String> lookup(String context, String filter) { if (null == filter) { return null; } LogUtils.debug(LOG, String.format("search DNs with context=%s, filter=%s", context, filter)); SearchControls ctrl = new SearchControls(); ctrl.setSearchScope(SearchControls.SUBTREE_SCOPE); ctrl.setReturningAttributes(new String[] {}); try { List<String> retVal = new ArrayList<String>(); NamingEnumeration<SearchResult> results = ctx.search(context, filter, ctrl); while (results.hasMore()) { retVal.add(results.next().getNameInNamespace()); } return retVal; } catch (NamingException e) { throw new SessionException(e.getMessage(), e); } } @Override public <T> List<T> searchIndirections(Class<T> clazz, String filter) { if (null == filter) { return null; } LogUtils.debug(LOG, String.format("search %s with filter=%s", clazz.getName(), filter)); OneMetaData oneMetaData = IndirectionsMetaData.get(clazz).getOne(); SearchControls ctrl = new SearchControls(); ctrl.setSearchScope(SearchControls.SUBTREE_SCOPE); ctrl.setReturningAttributes(new String[] { oneMetaData.getIdAttr(), oneMetaData.getIndirectionAttr() }); try { List<T> retVal = new ArrayList<T>(); NamingEnumeration<SearchResult> results = ctx.search(oneMetaData.getContext(), filter, ctrl); while (results.hasMore()) { SearchResult result = results.next(); retVal.add(fromAttributesToIndirections(clazz, result.getAttributes())); } return retVal; } catch (NamingException e) { throw new SessionException(e.getMessage(), e); } } @Override public <T> T uniqueSearch(Class<T> clazz, String filter) { return unique(search(clazz, filter)); } @Override public Map<String, Object> uniqueSearch(Class<?> clazz, String filter, String[] returningAttrs) { return unique(search(clazz, filter, returningAttrs)); } @Override public <T> T uniqueSearchIndirections(Class<T> clazz, String filter) { return unique(searchIndirections(clazz, filter)); } private static <T> T unique(List<T> list) { if (CollectionUtils.isEmpty(list)) { return null; } return list.get(0); } @Override public void close() { sessionCache.clear(); // release the internal JNDI connection if (bindToThreadLocal) { sessionFactory.closeCurrentSession(); } else { sessionFactory.closeLdapContext(ctx); } } private void connectIndirections(IndirectionsMetaData metaData, String oneDN, List<String> theOtherDNs) { if (StringUtils.isEmpty(oneDN) || CollectionUtils.isEmpty(theOtherDNs)) { LogUtils.debug(LOG, "connectIndirections: either one or theOther is empty, do nothing."); return; } LogUtils.debug(LOG, "connect " + oneDN + " and " + theOtherDNs); try { ctx.modifyAttributes(oneDN, new ModificationItem[] { ModUtils.add(metaData.getOne().getIndirectionAttr(), theOtherDNs) }); LogUtils.debug(LOG, String.format("connected: %s -> %s", oneDN, theOtherDNs)); for (String theOtherDN : theOtherDNs) { String indirectionAttr = metaData.getTheOther().dnToIndirectionAttr(theOtherDN); if (null == indirectionAttr) { LogUtils.debug(LOG, "no corresponding indirection configured for " + theOtherDN); continue; } ctx.modifyAttributes(theOtherDN, new ModificationItem[] { ModUtils.add(indirectionAttr, oneDN) }); LogUtils.debug(LOG, String.format("connected: %s -> %s", theOtherDN, oneDN)); } } catch (NamingException e) { throw new SessionException(e.getMessage(), e); } } private void disconnectIndirections(IndirectionsMetaData metaData, String oneDN, List<String> theOtherDNs) { if (StringUtils.isEmpty(oneDN) || CollectionUtils.isEmpty(theOtherDNs)) { LogUtils.debug(LOG, "disconnectIndirections: either one or theOther is empty, do nothing."); return; } LogUtils.debug(LOG, "disconnect " + oneDN + " and " + theOtherDNs); try { ctx.modifyAttributes(oneDN, new ModificationItem[] { ModUtils.remove(metaData.getOne().getIndirectionAttr(), theOtherDNs) }); LogUtils.debug(LOG, String.format("disconnected: %s -> %s", oneDN, theOtherDNs)); for (String theOtherDN : theOtherDNs) { String indirectionAttr = metaData.getTheOther().dnToIndirectionAttr(theOtherDN); if (null == indirectionAttr) { LogUtils.debug(LOG, "no corresponding indirection configured for " + theOtherDN); continue; } ctx.modifyAttributes(theOtherDN, new ModificationItem[] { ModUtils.remove(indirectionAttr, oneDN) }); LogUtils.debug(LOG, String.format("disconnected: %s -> %s", theOtherDN, oneDN)); } } catch (NamingException e) { throw new SessionException(e.getMessage(), e); } } private static Attributes fromTransientToAttributes(Object obj) { EntityMetaData metaData = EntityMetaData.get(ClassHelper.actualClass(obj)); Attributes toSaves = new BasicAttributes(); for (EntityPropertyMetaData propMetaData : metaData) { Object propValue = propMetaData.getter().get(obj); if (propValue == null) { continue; } // ignore empty list when create if (propMetaData.isMultiple() && ((List) propValue).isEmpty()) { continue; } Evaluator<String> evaluator = null; if (propMetaData.isReference()) { // reference property use dn as attribute value final EntityMetaData metaDataOfReferenceProp = EntityMetaData.get(propMetaData.getValueClass()); evaluator = new Evaluator<String>() { public String eval(Object obj) { Object idValue = metaDataOfReferenceProp.getIdProperty().getter().get(obj); if (null == idValue) { return null; } return DnHelper.build((String) idValue, metaDataOfReferenceProp.getManagedClass()); } }; } else { evaluator = createPropEvaluator(propMetaData); } if (!propMetaData.isMultiple()) { AttrUtils.putIfNotNull(toSaves, AttrUtils.create(propMetaData.getLdapPropName(), propValue, evaluator)); } else { AttrUtils.putIfNotNull(toSaves, AttrUtils.create(propMetaData.getLdapPropName(), (List) propValue, evaluator)); } } toSaves.put(AttrUtils.create("objectclass", metaData.objectClasses())); return toSaves; } private static List<ModificationItem> fromEntityToModificationItems(Object entity) { List<ModificationItem> mods = new ArrayList<ModificationItem>(); EntityMetaData metaData = EntityMetaData.get(ClassHelper.actualClass(entity)); Set<String> modifiedJavaBeanPropNames = EntityProxyFactory.getModifiedPropNames(entity); if (!CollectionUtils.isEmpty(modifiedJavaBeanPropNames)) { LogUtils.debug(LOG, "found modified properties for " + DnHelper.build(entity) + ": " + modifiedJavaBeanPropNames); // single valued properties for (String javaBeanPropName : modifiedJavaBeanPropNames) { EntityPropertyMetaData propMetaData = metaData.getPropertyByJavaBeanPropName(javaBeanPropName); if (propMetaData.isReadonly() || propMetaData.isMultiple()) { continue; } Object propValue = propMetaData.getter().get(entity); if (null == propValue) { mods.add(ModUtils.remove(propMetaData.getLdapPropName())); } else { CollectionUtils.addIfNotNull(mods, ModUtils.replace(propMetaData.getLdapPropName(), propValue, createPropEvaluator(propMetaData))); } } } // multiple valued properties for (EntityPropertyMetaData propMetaData : metaData) { if (propMetaData.isReadonly() || !propMetaData.isMultiple()) { continue; } List propValues = (List) propMetaData.getter().get(entity); if (null == propValues) { mods.add(ModUtils.remove(propMetaData.getLdapPropName())); } else { Evaluator<String> evaluator = createPropEvaluator(propMetaData); if (propValues instanceof MoniteredList) { MoniteredList moniteredList = (MoniteredList) propValues; CollectionUtils.addIfNotNull(mods, ModUtils.add(propMetaData.getLdapPropName(), moniteredList.getAddedElements(), evaluator)); CollectionUtils.addIfNotNull(mods, ModUtils.remove(propMetaData.getLdapPropName(), moniteredList.getRemovedElements(), evaluator)); } else { CollectionUtils.addIfNotNull(mods, ModUtils.replace(propMetaData.getLdapPropName(), propValues, evaluator)); } } } return mods; } private <T> T fromAttributesToEntity(Class<T> clazz, Attributes attributes) throws NamingException { try { Map.Entry<Object, SetterInterceptor> pair = EntityProxyFactory.getProxiedEntity(clazz); T entity = (T) pair.getKey(); EntityMetaData metaData = EntityMetaData.get(clazz); Set<String> multipleLdapAttrNames = new HashSet<String>(); for (EntityPropertyMetaData propMetaData : metaData) { if (propMetaData.isMultiple()) { multipleLdapAttrNames.add(propMetaData.getLdapPropName()); } } for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) { Attribute attr = attrs.next(); EntityPropertyMetaData propMetaData = metaData.getProperty(attr.getID()); if (null == propMetaData) { // current attribute exist in LDAP but not defined in our // POJO. continue; } if (propMetaData.isId()) { propMetaData.setter().set(entity, attr.get()); if (entity instanceof Distinguishable) { ((Distinguishable) entity) .setDN(attr.getID() + "=" + attr.get().toString() + "," + metaData.context()); } } else { List<String> attrValues = new ArrayList<String>(); for (NamingEnumeration<?> all = attr.getAll(); all.hasMore();) { attrValues.add(propMetaData.getSyntaxer().ldapStringToJavaString(all.next().toString())); } if (!propMetaData.isReference()) { if (!propMetaData.isMultiple()) { propMetaData.setter().set(entity, attrValues.get(0)); } else { propMetaData.setter().set(entity, new MoniteredList<String>(attrValues)); multipleLdapAttrNames.remove(propMetaData.getLdapPropName()); } } else { final Class<?> referenceType = propMetaData.getValueClass(); if (!propMetaData.isMultiple()) { propMetaData.setter().set(entity, EntityProxyFactory.getLazyLoadingProxiedEntity(this, referenceType, attrValues.get(0))); } else { List references = new ArrayList(); for (String dn : attrValues) { references.add( EntityProxyFactory.getLazyLoadingProxiedEntity(this, referenceType, dn)); } propMetaData.setter().set(entity, new MoniteredList(references)); multipleLdapAttrNames.remove(propMetaData.getLdapPropName()); } } } /* * The rest attribute names in multipleLdapAttrNames are those * not presented in LDAP side. In order to track what changes * occurred to these attributes, we need to use MoniteredList. */ for (String notPresentedMultipleLdapAttrName : multipleLdapAttrNames) { metaData.getProperty(notPresentedMultipleLdapAttrName).setter().set(entity, new MoniteredList()); } } /* * Once all the properties have been initialized, we should turn on * the switch of SetterInterceptor to monitor changes. */ pair.getValue().turnOn(); return entity; } catch (NamingException e) { LogUtils.debug(LOG, "failed to go through attributes when fromAttributesToEntity"); throw e; } } private Map<String, Object> fromAttributesToMap(Attributes attributes) throws NamingException { try { Map<String, Object> map = new HashMap<String, Object>(); for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) { Attribute attr = attrs.next(); map.put(attr.getID(), AttrUtils.valuesAsObject(attr)); } return map; } catch (NamingException e) { LogUtils.debug(LOG, "failed to go through attributes when fromAttributesToMap"); throw e; } } private Map<String, Object> fromAttributesToMap(Class<?> clazz, Attributes attributes) throws NamingException { try { Map<String, Object> map = new HashMap<String, Object>(); EntityMetaData metaData = EntityMetaData.get(clazz); for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) { Attribute attr = attrs.next(); EntityPropertyMetaData propMetaData = metaData.getProperty(attr.getID()); if (null == propMetaData) { continue; } List<String> attrValues = new ArrayList<String>(); for (NamingEnumeration<?> all = attr.getAll(); all.hasMore();) { attrValues.add(propMetaData.getSyntaxer().ldapStringToJavaString(all.next().toString())); } if (!propMetaData.isMultiple()) { map.put(attr.getID(), attrValues.get(0)); } else { map.put(attr.getID(), attrValues); } } return map; } catch (NamingException e) { LogUtils.debug(LOG, "failed to go through attributes when fromAttributesToMap"); throw e; } } private <T> T fromAttributesToIndirections(Class<T> clazz, Attributes attributes) throws NamingException { try { OneMetaData oneMetaData = IndirectionsMetaData.get(clazz).getOne(); Attribute idAttr = attributes.get(oneMetaData.getIdAttr()); Attribute indirectionAttr = attributes.get(oneMetaData.getIndirectionAttr()); T indirections = null; try { indirections = clazz.newInstance(); String dnOfOne = oneMetaData.getIdAttr() + "=" + String.valueOf(idAttr.get()) + "," + oneMetaData.getContext(); // @One won't be multiple. oneMetaData.setter().set(indirections, dnOfOne); // @TheOther is always multiple. TheOtherMetaData theOtherMetaData = IndirectionsMetaData.get(clazz).getTheOther(); if (null != indirectionAttr) { theOtherMetaData.setter().set(indirections, new MoniteredList(AttrUtils.values(indirectionAttr))); } else { theOtherMetaData.setter().set(indirections, new MoniteredList()); } } catch (InstantiationException e) { LogUtils.error(LOG, "cannot instantiate " + clazz, e); } catch (IllegalAccessException e) { LogUtils.error(LOG, "cannot instantiate " + clazz, e); } return IndirectionsProxyFactory.getProxiedIndirections(indirections); } catch (NamingException e) { LogUtils.debug(LOG, "failed to go through attributes when fromAttributesToIndirections"); throw e; } } private static final Evaluator<String> DN_EVALUATOR = new Evaluator<String>() { @Override public String eval(Object obj) { return DnHelper.build(obj); } }; private static Evaluator<String> createPropEvaluator(final EntityPropertyMetaData propMetaData) { if (propMetaData.isReference()) { return DN_EVALUATOR; } else { return new Evaluator<String>() { public String eval(Object obj) { return propMetaData.getSyntaxer().javaStringToLdapString(String.valueOf(obj)); } }; } } /** * An interface which marks an object as persistent. Client code should not * modeling any class via this interface as it will be the process of * {@link Session}. */ public interface Persistent { } /** * A list which provides additional features for monitoring element * additions and removals. */ @SuppressWarnings("serial") private static class MoniteredList<E> extends ArrayList<E> { private TreeSet<E> added = new TreeSet<E>(); private TreeSet<E> removed = new TreeSet<E>(); public MoniteredList() { super(); } public MoniteredList(Collection<? extends E> c) { super(c); } @Override public boolean add(E e) { boolean retVal = super.add(e); if (retVal) { added.add(e); } return retVal; } @Override public void add(int index, E element) { super.add(index, element); added.add(element); } @Override public boolean addAll(Collection<? extends E> c) { boolean retVal = super.addAll(c); if (retVal) { added.addAll(c); } return retVal; } @Override public boolean addAll(int index, Collection<? extends E> c) { boolean retVal = super.addAll(index, c); if (retVal) { added.addAll(c); } return retVal; } @Override public E remove(int index) { E retVal = super.remove(index); removed.add(retVal); return retVal; } @SuppressWarnings("unchecked") @Override public boolean remove(Object o) { boolean retVal = super.remove(o); if (retVal) { removed.add((E) o); } return retVal; } @SuppressWarnings("unchecked") @Override public boolean removeAll(Collection<?> c) { boolean retVal = super.removeAll(c); if (retVal) { removed.addAll((Collection<? extends E>) c); } return retVal; } public List<E> getAddedElements() { return new ArrayList<E>(added); } public List<E> getRemovedElements() { return new ArrayList<E>(removed); } /** * This method will be called once an entity has been updated * successfully. The changes must be cleared because we want to reuse * the entity this list belongs to. The reason is, if not cleared, the * changes will be gathered the next time when updating. */ public void clearChanges() { added.clear(); removed.clear(); } } }