You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by he...@apache.org on 2007/12/21 17:03:49 UTC
svn commit: r606228 [2/4] - in /directory/sandbox/hennejg/odm/trunk/src: ./
main/ main/java/ main/java/org/ main/java/org/apache/
main/java/org/apache/directory/ main/java/org/apache/directory/odm/
main/java/org/apache/directory/odm/auth/ main/resource...
Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ManyToManyMapping.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ManyToManyMapping.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ManyToManyMapping.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ManyToManyMapping.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,282 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.directory.odm;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.naming.NamingException;
+import javax.naming.directory.AttributeInUseException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The inverse side of a {@link GroupMapping}, i.e. the attribute pointing to
+ * the groups containing the object to which this attribute belongs.
+ *
+ * @author levigo
+ */
+public class ManyToManyMapping extends AttributeMapping {
+ private static final Logger logger = LoggerFactory
+ .getLogger(ManyToManyMapping.class);
+
+ private String filter;
+ private String memberField;
+ private final Class peerType;
+
+ private GroupMapping peerMapping;
+
+ public ManyToManyMapping(String fieldName, String fieldType)
+ throws ClassNotFoundException {
+ super(fieldName, Set.class.getName());
+ this.peerType = Class.forName(fieldType);
+
+ if (!Object.class.isAssignableFrom(this.peerType))
+ throw new IllegalArgumentException("The field " + fieldName
+ + " is not a subclass of Object");
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#valueFromAttributes(javax.naming.directory.Attribute)
+ */
+ @Override
+ protected Object valueFromAttributes(Attributes attributes, final Object o,
+ final Transaction tx) throws NamingException, DirectoryException {
+ // make proxy for lazy loading
+ return Proxy.newProxyInstance(o.getClass().getClassLoader(),
+ new Class[]{Set.class}, new InvocationHandler() {
+ private Set realObjectSet;
+
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ if (null == realObjectSet) {
+ final String dn = type.getDN(o);
+
+ DiropLogger.LOG.logReadComment("LAZY LOAD: {0} containing {1}",
+ peerType.getSimpleName(), dn);
+
+ realObjectSet = loadObjectSet(dn);
+
+ // set real loaded object to original instance.
+ setValue(o, realObjectSet);
+ }
+ return method.invoke(realObjectSet, args);
+ };
+ });
+ }
+
+ /**
+ * @param referencedDN
+ * @param tx TODO
+ * @return
+ * @throws DirectoryException
+ */
+ private Set loadObjectSet(String referencedDN) throws DirectoryException {
+ final Transaction tx = new Transaction(type.getMapping());
+ try {
+ referencedDN = peerMapping.getDirectoryFacade().fixNameCase(referencedDN);
+
+ return wrapValueSet(peerMapping.list(null != filter ? new Filter(filter,
+ referencedDN) : null, null, null, tx));
+ } catch (final NamingException e) {
+ throw new DirectoryException("Can't fix DN case for " + peerMapping);
+ } finally {
+ tx.commit();
+ }
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#dehydrate(org.openthinclient.common.directory.Object,
+ * javax.naming.directory.BasicAttributes)
+ */
+ @Override
+ public Object dehydrate(Object o, BasicAttributes a)
+ throws DirectoryException {
+ // nothing to do
+ return null;
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#cascadePostSave(org.openthinclient.common.directory.Object)
+ */
+ @Override
+ protected void cascadePostSave(Object o, Transaction tx, DirContext ctx)
+ throws DirectoryException {
+ // In preparation for SUITE-69: Check whether client code has modified the
+ // value set. Warn if a modification is detected.
+ // final Set newAssociations = (Set) getValue(o);
+ //
+ // if (null != newAssociations
+ // && !Proxy.isProxyClass(newAssociations.getClass())
+ // && newAssociations.size() > 0) {
+ // // Set is defined and not a proxy. Detect whether it is modifiable
+ // // by attempting to add a member to it.
+ // final Object something = newAssociations.iterator().next();
+ //
+ // try {
+ // // should be null-operation due to set-semantics
+ // newAssociations.add(something);
+ //
+ // // warn about transient change
+ // logger.warn("Changes to the field " + fieldName + " of type "
+ // + type.getMappedType() + " will not be persisted!");
+ // } catch (final UnsupportedOperationException e) {
+ // // expected/hoped for
+ // }
+ // }
+
+ // The following code has been commented out due to SUITE-69. It has,
+ // however, been left in place should there be the need to resurrect this
+ // functionality.
+
+ try {
+ // compare existing associations with the associations the saved object
+ // has
+ final Set newAssociations = (Set) getValue(o);
+
+ // if the associations aren't set at all, we don't care
+ if (null == newAssociations)
+ return;
+
+ if (null != newAssociations) {
+ // if the content is a proxy class, we don't have to save anything,
+ // since the association is unmodified.
+ if (Proxy.isProxyClass(newAssociations.getClass()))
+ return;
+
+ // save the association's members
+ for (final Object peer : newAssociations)
+ peerMapping.save(peer, null, tx);
+ }
+
+ // load existing associations
+ final String dn = peerMapping.getDirectoryFacade().fixNameCase(
+ type.getDN(o));
+ final Transaction nested = new Transaction(tx);
+ Set existing;
+ try {
+ existing = peerMapping.list(null != filter
+ ? new Filter(filter, dn)
+ : null, null, null, nested);
+ } catch (final DirectoryException e) {
+ nested.rollback();
+ throw e;
+ } catch (final RuntimeException e) {
+ nested.rollback();
+ throw e;
+ } finally {
+ nested.commit();
+ }
+
+ final List missing = new LinkedList();
+ if (null != newAssociations)
+ missing.addAll(newAssociations);
+
+ for (final Iterator i = missing.iterator(); i.hasNext();)
+ if (existing.remove(i.next()))
+ i.remove();
+
+ // missing now has the missing ones, existing the ones to be removed
+ for (final Iterator i = existing.iterator(); i.hasNext();) {
+ final Object group = i.next();
+ if (logger.isDebugEnabled())
+ logger.debug("Remove: " + group);
+ peerMapping.removeMember(group, memberField, dn, tx);
+ }
+ for (final Iterator i = missing.iterator(); i.hasNext();)
+ try {
+ final Object group = i.next();
+ if (logger.isDebugEnabled())
+ logger.debug("Save: " + group);
+ if (!peerMapping.isInDirectory(group, memberField, dn, tx))
+ peerMapping.addMember(group, memberField, dn, tx);
+ else
+ logger.error("Object already exists !!!");
+ } catch (final AttributeInUseException a) {
+ logger.error("Object already exists !!!", a);
+ }
+
+ } catch (final DirectoryException e) {
+ throw e;
+ } catch (final Exception e) {
+ throw new DirectoryException("Can't update many-to-many association", e);
+ }
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#checkNull(javax.naming.directory.Attributes)
+ */
+ @Override
+ protected boolean checkNull(Attributes a) {
+ return false;
+ }
+
+ public void setFilter(String filter) {
+ this.filter = filter;
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#initNewInstance(org.openthinclient.common.directory.Object)
+ */
+ @Override
+ protected void initNewInstance(Object instance) throws DirectoryException {
+ setValue(instance, wrapValueSet(new HashSet()));
+ }
+
+ private Set wrapValueSet(Set s) {
+ // commented-out until SUITE-69 is implemented.
+ // return Collections.unmodifiableSet(s);
+
+ return s;
+ }
+
+ public void setMemberField(String memberField) {
+ this.memberField = memberField;
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#initPostLoad()
+ */
+ @Override
+ protected void initPostLoad() {
+ super.initPostLoad();
+ final TypeMapping peer = type.getMapping().getMapping(peerType);
+ if (null == peer)
+ throw new IllegalStateException(this + ": no mapping for peer type "
+ + peerType);
+
+ if (!(peer instanceof GroupMapping))
+ throw new IllegalStateException("many-to-many-mapping " + this
+ + " needs a group as a partner, not a " + peer);
+
+ this.peerMapping = (GroupMapping) peer;
+
+ peer.addReferrer(this);
+ }
+}
Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ManyToManyMapping.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ManyToOneMapping.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ManyToOneMapping.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ManyToOneMapping.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ManyToOneMapping.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.directory.odm;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author levigo
+ */
+public class ManyToOneMapping extends ReferenceAttributeMapping {
+ private static final Logger logger = LoggerFactory.getLogger(ManyToOneMapping.class);
+
+ private final Class refereeType;
+ private TypeMapping refereeMapping;
+
+ public ManyToOneMapping(String fieldName, String fieldType)
+ throws ClassNotFoundException {
+ super(fieldName, fieldType);
+ this.refereeType = Class.forName(fieldType);
+
+ if (!Object.class.isAssignableFrom(this.refereeType))
+ throw new IllegalArgumentException("The field " + fieldName
+ + " is not a subclass of Object");
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#valueFromAttributes(javax.naming.directory.Attribute)
+ */
+ @Override
+ protected Object valueFromAttributes(Attributes attributes, Object o,
+ Transaction tx) throws NamingException, DirectoryException {
+ final Attribute attribute = attributes.get(fieldName);
+ if (null != attribute) {
+ final String dn = attribute.get().toString();
+ try {
+ return type.getMapping() //
+ .getMapping(getFieldType(), dn) //
+ .load(dn, tx);
+ } catch (final NameNotFoundException e) {
+ logger.warn("Referenced object for many-to-one mapping not found: "
+ + dn);
+ }
+ }
+
+ return null;
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#initPostLoad()
+ */
+ @Override
+ protected void initPostLoad() {
+ super.initPostLoad();
+ final TypeMapping child = type.getMapping().getMapping(refereeType);
+ if (null == child)
+ throw new IllegalStateException(this + ": no mapping for peer type "
+ + refereeType);
+
+ this.refereeMapping = child;
+
+ child.addReferrer(this);
+ }
+
+ /*
+ * @see org.apache.directory.odm.AttributeMapping#getValue(java.lang.Object)
+ */
+ @Override
+ protected Object getValue(Object o) throws DirectoryException {
+ final Object referenced = super.getValue(o);
+ if (null == referenced)
+ return null;
+ return refereeMapping.getDN(referenced);
+ }
+
+ /*
+ * @see org.apache.directory.odm.AttributeMapping#cascadePreSave(java.lang.Object,
+ * org.apache.directory.odm.Transaction)
+ */
+ @Override
+ protected void cascadePreSave(Object o, Transaction tx)
+ throws DirectoryException {
+ super.cascadePreSave(o, tx);
+ final Object referenced = super.getValue(o);
+ if (null != referenced)
+ refereeMapping.save(referenced, null, tx);
+ }
+
+ @Override
+ Cardinality getCardinality() {
+ return Cardinality.ZERO_OR_ONE;
+ }
+}
Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ManyToOneMapping.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Mapping.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Mapping.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Mapping.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Mapping.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,817 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.directory.odm;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.naming.InvalidNameException;
+import javax.naming.Name;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+
+import org.apache.directory.odm.TypeMapping.SearchScope;
+import org.exolab.castor.mapping.MappingException;
+import org.exolab.castor.xml.MarshalException;
+import org.exolab.castor.xml.Unmarshaller;
+import org.exolab.castor.xml.ValidationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.InputSource;
+
+/**
+ * @author levigo
+ */
+public class Mapping {
+ /**
+ * For unit-test purposes only...
+ */
+ public static boolean disableCache = false;
+
+ private static final Logger logger = LoggerFactory.getLogger(Mapping.class);
+
+ /**
+ * Property key to be used when all directory operations should be forced into
+ * a single-threaded access model. If this property is set to a non-<code>null</code>
+ * value, all accesses are synchronized. This will limit the number of
+ * parallel directory operations effected by the mapping to one.
+ */
+ public static final String PROPERTY_FORCE_SINGLE_THREADED = "ldap.mapping.single-treaded";
+
+ /**
+ * Load an LDAP Mapping
+ *
+ * @param path
+ * @return
+ * @throws IOException
+ * @throws MappingException
+ * @throws MarshalException
+ * @throws ValidationException
+ * @throws MarshalException
+ */
+ public static Mapping load(InputStream is) throws IOException,
+ MappingException, ValidationException, MarshalException {
+ // Create a Reader to the file to unmarshal from
+ final InputStreamReader reader = new InputStreamReader(is);
+
+ // Create a new Unmarshaller
+ final org.exolab.castor.mapping.Mapping m = new org.exolab.castor.mapping.Mapping();
+ m.loadMapping(new InputSource(Mapping.class
+ .getResourceAsStream("ldap-mapping.xml")));
+ final Unmarshaller unmarshaller = new Unmarshaller(m);
+
+ // Unmarshal the configuration object
+ final Mapping loadedMapping = (Mapping) unmarshaller.unmarshal(reader);
+
+ return loadedMapping;
+ }
+
+ /**
+ * The default type mappers, i.e. the ones to be used, when no explicit target
+ * directory is selected.
+ */
+ private final Map<Class, TypeMapping> defaultMappers = new HashMap<Class, TypeMapping>();
+
+ private boolean initialized;
+
+ /**
+ * All mappers mamaged by this mapping
+ */
+ private final Set<TypeMapping> mappers = new HashSet<TypeMapping>();
+
+ /**
+ * The mappers indexed by connection descriptor (i.e. target Directory Server)
+ */
+ private final Map<DirectoryFacade, Set<TypeMapping>> mappersByDirectory = new HashMap<DirectoryFacade, Set<TypeMapping>>();
+
+ /**
+ * The mappers indexed by mapped type
+ */
+ private final Map<Class, Set<TypeMapping>> mappersByType = new HashMap<Class, Set<TypeMapping>>();
+
+ /**
+ * The Mapping's name.
+ */
+ private String name;
+
+ // FIXME: make this configurable
+ private final SecondLevelCache secondLevelCache = new EhCacheSecondLevelCache();
+
+ public Mapping() {
+
+ }
+
+ public Mapping(Mapping m) {
+ this();
+
+ for (final TypeMapping tm : m.mappers)
+ try {
+ this.add(tm.clone());
+ } catch (final CloneNotSupportedException e1) {
+ // should not happen. If it does, we're doomed anyway.
+ throw new RuntimeException(e1);
+ }
+
+ initialize();
+ }
+
+ /**
+ * Add a type mapping.
+ *
+ * @param typeMapping
+ */
+ public void add(TypeMapping typeMapping) {
+ if (mappers.contains(typeMapping))
+ throw new IllegalArgumentException(
+ "The specified TypeMapping already contained in this mapping");
+
+ typeMapping.setMapping(this);
+
+ mappers.add(typeMapping);
+ defaultMappers.put(typeMapping.getMappedType(), typeMapping);
+
+ // maintain index by class
+ Set<TypeMapping> mappersForClass = mappersByType.get(typeMapping
+ .getMappedType());
+ if (null == mappersForClass) {
+ mappersForClass = new HashSet<TypeMapping>();
+ mappersByType.put(typeMapping.getMappedType(), mappersForClass);
+ }
+
+ mappersForClass.add(typeMapping);
+
+ // maintain index by directory
+ final DirectoryFacade lcd = typeMapping.getDirectoryFacade();
+ if (null != lcd) {
+ Set<TypeMapping> mappersForConnection = mappersByDirectory.get(lcd);
+ if (null == mappersForConnection) {
+ mappersForConnection = new HashSet<TypeMapping>();
+ mappersByDirectory.put(lcd, mappersForConnection);
+ }
+ mappersForConnection.add(typeMapping);
+ }
+ }
+
+ /**
+ * Close this mapping. Closing a mapping currently has the sole effect of
+ * purging the cache.
+ */
+ public void close() {
+ try {
+ if (null != secondLevelCache)
+ secondLevelCache.clear();
+ } catch (final Exception e) {
+ logger.error("Can't purge cache", e);
+ }
+ }
+
+ /**
+ * Create an object (new instance) of the given type and initialize the RDN
+ * atttribute.
+ *
+ * @param type
+ * @return
+ * @throws DirectoryException
+ */
+ public <T> T create(Class<T> type) throws DirectoryException {
+ if (logger.isDebugEnabled())
+ logger.debug("create(): create=" + type);
+
+ final TypeMapping tm = defaultMappers.get(type);
+ if (null == tm)
+ throw new IllegalArgumentException("No mapping for class " + type);
+
+ return (T) tm.create();
+ }
+
+ /**
+ * Remove the given object from the directory.
+ *
+ * @param object
+ * @throws DirectoryException
+ */
+ public boolean delete(Object object) throws DirectoryException {
+ if (logger.isDebugEnabled())
+ logger.debug("delete(): type=" + object.getClass());
+
+ final TypeMapping tm = defaultMappers.get(object.getClass());
+ if (null == tm)
+ throw new IllegalArgumentException("No mapping for class "
+ + object.getClass());
+
+ final Transaction tx = new Transaction(this);
+ try {
+ return tm.delete(object, tx);
+ } catch (final DirectoryException e) {
+ tx.rollback();
+ throw e;
+ } catch (final RuntimeException e) {
+ tx.rollback();
+ throw e;
+ } finally {
+ if (!tx.isClosed())
+ tx.commit();
+ }
+ }
+
+ /**
+ * Get set of {@link TypeMapping}s managed by this Mapping.
+ *
+ * @return
+ */
+ Set<TypeMapping> getMappers() {
+ return mappers;
+ }
+
+ /**
+ * Return the (default) TypeMapping for a given class.
+ *
+ * @param c
+ * @return
+ */
+ TypeMapping getMapping(Class c) {
+ return defaultMappers.get(c);
+ }
+
+ /**
+ * Find the TypeMapping to use for a given mapped type where the connection
+ * descriptor is the same as the specified one.
+ *
+ * @param type the mapped type
+ * @param baseDN the base DN of the object to be handled or <code>null</code>
+ * if it is not (yet?) known.
+ *
+ * @return
+ * @throws NamingException
+ */
+ TypeMapping getMapping(Class type, DirectoryFacade connectionDescriptor) {
+ final Set<TypeMapping> mappers = mappersByDirectory
+ .get(connectionDescriptor);
+ for (final TypeMapping tm : mappers)
+ if (tm.getMappedType().equals(type))
+ return tm;
+
+ throw new IllegalArgumentException(
+ "No mapping for the specified type and connection descriptor");
+ }
+
+ /**
+ * Find the TypeMapping to use for a given mapped type. Refined by base DN if
+ * appropriate/necessary.
+ *
+ * @param type the mapped type
+ * @param baseDN the base DN of the object to be handled or <code>null</code>
+ * if it is not (yet?) known.
+ *
+ * @return
+ * @throws NamingException
+ */
+ TypeMapping getMapping(Class type, String baseDN) throws NamingException {
+ // no base DN -> use default mapping
+ if (null == baseDN)
+ return defaultMappers.get(type);
+
+ // try to find suitable mapping, assuming that the base DN is absolute
+ final Set<TypeMapping> mappersForClass = mappersByType.get(type);
+ for (final TypeMapping tm : mappersForClass)
+ if (tm.getDirectoryFacade().contains(
+ tm.getDirectoryFacade().getNameParser().parse(baseDN)))
+ return tm;
+
+ // no cigar? fall back to default
+ return defaultMappers.get(type);
+ }
+
+ /**
+ * Return the mapping for the object at a given DN. In order to determine the
+ * mapping, the object's objectClasses need to be loaded.
+ *
+ * @param dn
+ * @param objectClasses
+ * @param tx current transaction
+ * @return
+ * @throws DirectoryException
+ * @throws NamingException
+ */
+ TypeMapping getMapping(String dn, Transaction tx) throws DirectoryException,
+ NamingException {
+ for (final Map.Entry<DirectoryFacade, Set<TypeMapping>> e : mappersByDirectory
+ .entrySet()) {
+ // check whether the directory contains the dn
+ final DirectoryFacade df = e.getKey();
+ final Name parsedDN = df.getNameParser().parse(dn);
+ if (df.contains(parsedDN)) {
+ // load the object and determine the object class
+ final DirContext ctx = tx.getContext(df);
+ final String[] attributes = {"objectClass"};
+
+ DiropLogger.LOG.logGetAttributes(dn, attributes, "determining mapping");
+
+ final Attributes a = ctx.getAttributes(df.makeRelativeName(dn),
+ attributes);
+ final Attribute objectClasses = a.get("objectClass");
+
+ final Set<TypeMapping> mappings = e.getValue();
+
+ final TypeMapping match = getMapping(parsedDN, objectClasses, mappings);
+ if (null != match)
+ return match;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Find the best TypeMapping for an object described by its DN an object
+ * classes from a set of mappings.
+ *
+ * @param parsedDN
+ * @param objectClasses
+ * @param mappings
+ * @return
+ * @throws NamingException
+ * @throws InvalidNameException
+ */
+ private TypeMapping getMapping(final Name parsedDN,
+ final Attribute objectClasses, Set<TypeMapping> mappings)
+ throws NamingException, InvalidNameException {
+ // build list of mapping candidates. There may be more than one!
+ final List<TypeMapping> candidates = new ArrayList<TypeMapping>();
+ for (final TypeMapping tm : mappings)
+ if (tm.matchesKeyClasses(objectClasses))
+ candidates.add(tm);
+
+ // if there is only one match, return it
+ if (candidates.size() == 1)
+ return candidates.get(0);
+
+ // if more than one match, select best one by base RDN
+ for (final TypeMapping tm : candidates)
+ if (tm.getBaseRDN() != null)
+ if (parsedDN.startsWith(tm.getDefaultBaseName()))
+ return tm;
+
+ // no "best" match -> just use first one
+ if (candidates.size() > 0)
+ return candidates.get(0);
+
+ return null;
+ }
+
+ /**
+ * Get a map of {@link TypeMapping}s by mapped class.
+ *
+ * @return
+ */
+ public Map<Class, TypeMapping> getTypes() {
+ return Collections.unmodifiableMap(defaultMappers);
+ }
+
+ /**
+ * Initialize this Mapping. Used after unmarshalling it from XML.
+ */
+ public void initialize() {
+ if (initialized)
+ return;
+
+ for (final TypeMapping m : defaultMappers.values())
+ m.initPostLoad();
+
+ initialized = true;
+
+ if (logger.isDebugEnabled())
+ logger.debug("LDAP mapping initialized");
+ }
+
+ /**
+ * List all objects of the given type located at the default base DN for the
+ * given type.
+ *
+ * @param type
+ * @return
+ * @throws DirectoryException
+ */
+ public <T> Set<T> list(Class<T> type) throws DirectoryException {
+ if (!initialized)
+ throw new DirectoryException(
+ "Mapping is not yet initialized - call initialize() first");
+
+ return list(type, null, null, null);
+ }
+
+ /**
+ * List objects of the given type at or below the given search base using the
+ * given context.
+ *
+ * @param <T>
+ * @param type
+ * @param filter
+ * @param baseDN
+ * @param scope
+ * @return
+ * @throws DirectoryException
+ */
+ @SuppressWarnings("cast")
+ public <T> Set<T> list(Class<T> type, Filter filter, String baseDN,
+ SearchScope scope) throws DirectoryException {
+ if (!initialized)
+ throw new DirectoryException(
+ "Mapping is not yet initialized - call initialize() first");
+
+ if (logger.isDebugEnabled())
+ logger.debug("list(): type=" + type + ", filter=" + filter
+ + ", searchBase=" + baseDN);
+
+ // get mapper. try to find one for the specified search base first
+ TypeMapping tm;
+ try {
+ tm = getMapping(type, baseDN);
+ } catch (final NamingException e) {
+ throw new DirectoryException(
+ "Can't determine TypeMapping for this type and search base", e);
+ }
+
+ // fall back to default mapping if not found
+ if (null == tm)
+ tm = defaultMappers.get(type);
+
+ if (null == tm)
+ throw new IllegalArgumentException("No mapping for class " + type);
+
+ final Transaction tx = new Transaction(this);
+ try {
+ return (Set<T>) tm.list(filter, baseDN, scope, tx);
+ } catch (final DirectoryException e) {
+ tx.rollback();
+ throw e;
+ } catch (final RuntimeException e) {
+ tx.rollback();
+ throw e;
+ } finally {
+ if (!tx.isClosed())
+ tx.commit();
+ }
+ }
+
+ /**
+ * Load an object of the given type from the given dn.
+ *
+ * @param type the (expected) type
+ * @param dn the object's dn
+ * @return
+ * @throws DirectoryException
+ */
+ public <T> T load(Class<T> type, String dn) throws DirectoryException {
+ return load(type, dn, false);
+ }
+
+ /**
+ * Load an object of the given type from the given dn.
+ *
+ * @param type the (expected) type
+ * @param dn the object's dn
+ * @param noCache if <code>true</code> the cache will not be consulted for
+ * this object.
+ * @return
+ * @throws DirectoryException
+ */
+ public <T> T load(Class<T> type, String dn, boolean noCache)
+ throws DirectoryException {
+ if (!initialized)
+ throw new DirectoryException(
+ "Mapping is not yet initialized - call initialize() first");
+
+ if (logger.isDebugEnabled())
+ logger.debug("load(): type=" + type + ", dn=" + dn);
+
+ TypeMapping tm;
+ try {
+ tm = getMapping(type, dn);
+ } catch (final NamingException e) {
+ throw new DirectoryException(
+ "Can't determine TypeMapping for this type and DN", e);
+ }
+
+ if (null == tm)
+ throw new IllegalArgumentException("No mapping for class " + type);
+
+ final Transaction tx = new Transaction(this, noCache);
+ try {
+ return (T) tm.load(dn, tx);
+ } catch (final DirectoryException e) {
+ tx.rollback();
+ throw e;
+ } catch (final RuntimeException e) {
+ tx.rollback();
+ throw e;
+ } finally {
+ if (!tx.isClosed())
+ tx.commit();
+ }
+ }
+
+ /**
+ * Refresh the given object's state from the directory. The object must
+ * already have a DN for this operation to succeed. This operations always
+ * by-passes the cache, making sure that the object's state after the refresh
+ * is consistent with the directory.
+ *
+ * @param a
+ * @throws DirectoryException
+ */
+ public void refresh(Object o) throws DirectoryException {
+ if (!initialized)
+ throw new DirectoryException(
+ "Mapping is not yet initialized - call initialize() first");
+
+ if (logger.isDebugEnabled())
+ logger.debug("refresh(): object=" + o);
+
+ final TypeMapping tm = defaultMappers.get(o.getClass());
+ if (null == tm)
+ throw new IllegalArgumentException("No mapping for class " + o.getClass());
+
+ final Transaction tx = new Transaction(this, true);
+ try {
+ tm.refresh(o, tx);
+ } catch (final DirectoryException e) {
+ tx.rollback();
+ throw e;
+ } catch (final RuntimeException e) {
+ tx.rollback();
+ throw e;
+ } finally {
+ if (!tx.isClosed())
+ tx.commit();
+ }
+ }
+
+ /**
+ * Remove the given {@link TypeMapping}.
+ *
+ * @param tm
+ */
+ public void remove(TypeMapping tm) {
+ if (!mappers.remove(tm))
+ return;
+
+ defaultMappers.remove(tm.getMappedType());
+
+ // maintain index by type
+ final Set<TypeMapping> forType = mappersByType.get(tm.getMappedType());
+ if (null != forType) {
+ forType.remove(tm);
+ if (forType.isEmpty())
+ mappersByType.remove(tm.getMappedType());
+ }
+
+ // maintain index by connection
+ final Set<TypeMapping> forConnection = mappersByDirectory.get(tm
+ .getDirectoryFacade());
+ if (null != forConnection) {
+ forConnection.remove(tm);
+ if (forConnection.isEmpty())
+ mappersByDirectory.remove(tm.getDirectoryFacade());
+ }
+ }
+
+ /**
+ * Save the given object to the default base DN appropriate for the given
+ * object type.
+ *
+ * @param o the object to be saved
+ * @throws DirectoryException
+ */
+ public void save(Object o) throws DirectoryException {
+ save(o, null);
+ }
+
+ /**
+ * Save the given object to the given base DN. The object DN will be made up
+ * from the base DN and the object's RDN. If the object was already persistent
+ * and is therefore only updated, specifying the base DN will have no effect.
+ *
+ * @param o the object to be saved
+ * @param baseDN the base DN at which to save the object
+ * @throws DirectoryException
+ */
+ public void save(Object o, String baseDN) throws DirectoryException {
+ if (!initialized)
+ throw new DirectoryException(
+ "Mapping is not yet initialized - call initialize() first");
+
+ if (logger.isDebugEnabled())
+ logger.debug("save(): object=" + o + ", baseDN=" + baseDN);
+ final TypeMapping tm = defaultMappers.get(o.getClass());
+ if (null == tm)
+ throw new IllegalArgumentException("No mapping for class " + o.getClass());
+
+ final Transaction tx = new Transaction(this);
+ try {
+ tm.save(o, baseDN, tx);
+ } catch (final DirectoryException e) {
+ tx.rollback();
+ throw e;
+ } catch (final RuntimeException e) {
+ tx.rollback();
+ throw e;
+ } finally {
+ if (!tx.isClosed())
+ tx.commit();
+ }
+ }
+
+ /**
+ * Set the directory connection to be used.
+ *
+ * @param lcd
+ *
+ * @see #setDirectoryFacade(DirectoryFacade)
+ */
+ public void setConnectionDescriptor(LDAPConnectionDescriptor lcd) {
+ setDirectoryFacade(lcd.createDirectoryFacade());
+ }
+
+ /**
+ * Set the {@link DirectoryFacade} to be used for all accesses to the
+ * directory. This method may be used instead of
+ * {@link #setConnectionDescriptor(LDAPConnectionDescriptor)} if a
+ * {@link DirectoryFacade} has already been obtained otherwise.
+ *
+ * @param env
+ *
+ * @see #setConnectionDescriptor(LDAPConnectionDescriptor)
+ */
+ public void setDirectoryFacade(DirectoryFacade lcd) {
+ // iterate over a copy to prevent a CME
+ for (final TypeMapping tm : new ArrayList<TypeMapping>(mappers)) {
+ // remove and add to preserve mapping indexes
+ remove(tm);
+ tm.setDirectoryFacade(lcd);
+ add(tm);
+ }
+ }
+
+ /**
+ * Clear/update all references to the specified dn. This is usually used in
+ * response to an object deletion/rename.
+ *
+ * @param tx
+ * @param oldDN the name of the existing object being referred to
+ * @param newDN the new name of the object, or <code>null</code> if the
+ * object has been deleted.
+ * @throws DirectoryException
+ * @throws NamingException
+ */
+ void updateReferences(Transaction tx, String oldDN, String newDN)
+ throws DirectoryException, NamingException {
+ // iterate over target directories, so that we can query the referrers
+ // efficiently using just one query per directory.
+ for (final Map.Entry<DirectoryFacade, Set<TypeMapping>> e : mappersByDirectory
+ .entrySet()) {
+ final Set<TypeMapping> mappers = e.getValue();
+ final DirectoryFacade directory = e.getKey();
+
+ // Build list of referrer attributes.
+ final Set<ReferenceAttributeMapping> refererAttributes = new HashSet<ReferenceAttributeMapping>();
+ for (final TypeMapping m : mappers)
+ m.collectRefererAttributes(refererAttributes);
+
+ // compress references into set of attribute names and a set of type
+ // mappings (not all types have references at all!)
+ final Set<String> attributeNames = new HashSet<String>();
+ final Set<TypeMapping> effectiveMappers = new HashSet<TypeMapping>();
+ for (final ReferenceAttributeMapping ra : refererAttributes) {
+ attributeNames.add(ra.getFieldName());
+ effectiveMappers.add(ra.getTypeMapping());
+ }
+
+ // build filter expression
+ final DirContext ctx = tx.getContext(directory);
+ final StringBuilder sb = new StringBuilder("(|");
+ for (final String name : attributeNames)
+ sb.append("(").append(name).append("=").append(oldDN).append(")");
+ sb.append(")");
+
+ // we query by referrer attribute name only. Theoretically, we would also
+ // need to use the object class in the query, but we can probably get
+ // away with this simplification in all practical cases.
+ final SearchControls sc = new SearchControls();
+ sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
+ sc.setReturningAttributes(new String[refererAttributes.size()]);
+ sc.setDerefLinkFlag(false);
+
+ final String filter = sb.toString();
+
+ DiropLogger.LOG.logSearch("", filter, null, sc, "searching references");
+
+ // issue query to find referencing objects
+ final NamingEnumeration<SearchResult> ne = ctx.search("", filter, sc);
+
+ while (ne.hasMore()) {
+ final SearchResult result = ne.next();
+ final Attributes attributes = result.getAttributes();
+ List<ModificationItem> mods = null;
+
+ // Determine applicable TypeMapper for the referencing object
+ final TypeMapping m = getMapping(directory.makeAbsoluteName(result
+ .getName()), attributes.get("objectClass"), mappers);
+
+ if (null == m) {
+ logger
+ .warn("Could not determine TypeMapping for referencing object at "
+ + result.getName());
+ continue;
+ }
+
+ for (final ReferenceAttributeMapping ra : refererAttributes) {
+ // check whether the reference matches the type of object we found
+ if (ra.getTypeMapping() != m)
+ continue;
+
+ final Attribute attr = attributes.get(ra.getFieldName());
+ if (attr != null) {
+ // for rename: re-add new name
+ if (null != newDN && null == mods) {
+ mods = new LinkedList<ModificationItem>();
+
+ attr.remove(oldDN);
+ attr.add(newDN);
+
+ mods
+ .add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr));
+ }
+
+ if (null == mods) {
+ mods = new LinkedList<ModificationItem>();
+ attr.remove(oldDN);
+
+ // check whether we need to re-add the dummy member
+ if (attr.size() == 0
+ && (ra.getCardinality() == Cardinality.ONE || ra
+ .getCardinality() == Cardinality.ONE_OR_MANY))
+ attr.add(directory.getDummyMember());
+
+ mods
+ .add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr));
+ }
+ }
+ }
+
+ if (null != mods) {
+ final ModificationItem[] modsArray = mods
+ .toArray(new ModificationItem[mods.size()]);
+
+ DiropLogger.LOG.logModify(result.getName(), modsArray,
+ "cascading update due to DN change of referenced object");
+
+ ctx.modifyAttributes(result.getName(), modsArray);
+ }
+ }
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ SecondLevelCache getSecondLevelCache() {
+ return secondLevelCache;
+ }
+}
Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Mapping.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/OneToManyMapping.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/OneToManyMapping.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/OneToManyMapping.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/OneToManyMapping.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,345 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.directory.odm;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.naming.Name;
+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.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class maps the outgoing side of one-to-many (which are actually always
+ * many-to-many) style mappings. It usually corresponds to the attribute holding
+ * the member reference of a group type mapped by {@link GroupMapping}.
+ *
+ * @author levigo
+ */
+public class OneToManyMapping extends ReferenceAttributeMapping {
+
+ private static final Logger logger = LoggerFactory.getLogger(OneToManyMapping.class);
+
+ private final Class memberType;
+ private TypeMapping memberMapping;
+
+ private static final Set EMPTY = Collections.unmodifiableSet(new HashSet());
+
+ public OneToManyMapping(String fieldName, String memberType)
+ throws ClassNotFoundException {
+ super(fieldName, Set.class.getName());
+ if (memberType.equals("*"))
+ this.memberType = Object.class;
+ else
+ this.memberType = Class.forName(memberType);
+
+ this.cardinality = Cardinality.ONE_OR_MANY;
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#valueFromAttributes(javax.naming.directory.Attribute)
+ */
+ @Override
+ protected Object valueFromAttributes(final Attributes attributes,
+ final Object o, final Transaction tx) throws NamingException,
+ DirectoryException {
+ // make proxy for lazy loading
+ return Proxy.newProxyInstance(o.getClass().getClassLoader(),
+ new Class[]{getFieldType()}, new InvocationHandler() {
+ private Set realMemberSet;
+
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ if (null == realMemberSet) {
+ if (DiropLogger.LOG.isReadEnabled())
+ DiropLogger.LOG.logReadComment(
+ "LAZY LOAD: collection for {0}: {1}", fieldName, type
+ .getDN(o));
+
+ realMemberSet = loadMemberSet(attributes);
+ setValue(o, realMemberSet);
+ }
+ return method.invoke(realMemberSet, args);
+ };
+ });
+ }
+
+ /**
+ * @param attributes
+ * @param tx TODO
+ * @return
+ * @throws DirectoryException
+ */
+ private Set loadMemberSet(Attributes attributes) throws DirectoryException {
+
+ final Attribute membersAttribute = attributes.get(fieldName);
+
+ final Transaction tx = new Transaction(type.getMapping());
+ try {
+ final Set results = new HashSet();
+ if (null != membersAttribute) {
+ final NamingEnumeration<?> e = membersAttribute.getAll();
+ try {
+
+ while (e.hasMore()) {
+ final String memberDN = e.next().toString();
+
+ // ignore dummy
+ if (type.getDirectoryFacade().isDummyMember(memberDN))
+ continue;
+
+ TypeMapping mm = this.memberMapping;
+ if (null == mm)
+ try {
+ mm = type.getMapping().getMapping(memberDN, tx);
+ } catch (final NameNotFoundException f) {
+ logger.warn("Ignoring nonexistant referenced object: "
+ + memberDN);
+ continue;
+ }
+
+ if (null == mm) {
+ logger.warn(this + ": can't determine mapping type for dn="
+ + memberDN);
+ continue;
+ }
+
+ try {
+ results.add(mm.load(memberDN, tx));
+ } catch (final DirectoryException f) {
+ if (f.getCause() != null
+ && f.getCause() instanceof NameNotFoundException)
+ logger.warn("Ignoring nonexistant referenced object: "
+ + memberDN);
+ else
+ throw f;
+ }
+ }
+ } finally {
+ e.close();
+ }
+ }
+ return results;
+ } catch (final NamingException e) {
+ throw new DirectoryException(
+ "Exception during lazy loading of group members", e);
+ } finally {
+ tx.commit();
+ }
+ }
+
+ @Override
+ protected void cascadePreSave(Object o, Transaction tx)
+ throws DirectoryException {
+ super.cascadePreSave(o, tx);
+
+ Set memberSet = (Set) getValue(o);
+ if (null == memberSet)
+ memberSet = EMPTY; // empty set
+
+ // if we still see the unchanged proxy, we're done!
+ if (!Proxy.isProxyClass(memberSet.getClass()))
+ for (final Object member : memberSet) {
+ // make sure that the member has already been saved
+ final TypeMapping mappingForMember = getMappingForMember(member);
+ final String dn = mappingForMember.getDN(member);
+ if (null == dn)
+ mappingForMember.save(member, null, tx);
+ }
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#dehydrate(org.openthinclient.common.directory.Object,
+ * javax.naming.directory.BasicAttributes)
+ */
+ @Override
+ public Object dehydrate(Object o, BasicAttributes a)
+ throws DirectoryException, NamingException {
+ Set memberSet = (Set) getValue(o);
+
+ if (null == memberSet)
+ memberSet = EMPTY; // empty set
+
+ // if we still see the unchanged proxy, we're done!
+ if (!Proxy.isProxyClass(memberSet.getClass())) {
+ // compile list of memberDNs
+ // Attribute memberDNs = null;
+ final Attribute memberDNs = new BasicAttribute(fieldName);
+
+ if (memberSet.isEmpty()) {
+ // do we need a dummy entry?
+ if (cardinality == Cardinality.ONE_OR_MANY)
+ memberDNs.add(type.getDirectoryFacade().getDummyMember());
+ } else
+ for (final Object member : memberSet)
+ try {
+ final TypeMapping mappingForMember = getMappingForMember(member);
+
+ final String memberDN = type.getDirectoryFacade().fixNameCase(
+ mappingForMember.getDN(member));
+
+ memberDNs.add(memberDN);
+ } catch (final NamingException e) {
+ throw new DirectoryException("Can't dehydrate", e);
+ }
+
+ // we only add the attribute if it has members
+ if (memberDNs.size() > 0)
+ a.put(memberDNs);
+
+ } else
+ a.put(new BasicAttribute(fieldName,
+ TypeMapping.ATTRIBUTE_UNCHANGED_MARKER));
+
+ return memberSet;
+ }
+
+ private TypeMapping getMappingForMember(Object member)
+ throws DirectoryException {
+ TypeMapping mappingForMember = memberMapping;
+
+ // for a generic mapping we have no way of accessing
+ // the DN of the member object without fetching at least the default
+ // mapping for it.
+ if (null == mappingForMember)
+ mappingForMember = type.getMapping().getMapping(member.getClass());
+
+ if (null == mappingForMember)
+ throw new DirectoryException(
+ "One-to-many associaction contains a member of type "
+ + member.getClass() + " for which I don't have a mapping.");
+
+ final String dn = mappingForMember.getDN(member);
+
+ // if the member doesn't have a dn, we must resort to the default mapping
+ if (null == dn)
+ return mappingForMember;
+
+ // if the mapping we found doesn't match the dn, we need
+ // to refine it: the member may point to a non-default directory
+ // for the mapped type.
+ Name parsedDN;
+ try {
+ parsedDN = mappingForMember.getDirectoryFacade().getNameParser()
+ .parse(dn);
+ if (!mappingForMember.getDirectoryFacade().contains(parsedDN)) {
+ mappingForMember = type.getMapping().getMapping(member.getClass(), dn);
+
+ // re-parse, because the provider might be different.
+ // We may want to get rid of other provider types (besides SUN),
+ // because of this unnecessary complexity.
+ parsedDN = mappingForMember.getDirectoryFacade().getNameParser().parse(
+ dn);
+ }
+
+ return mappingForMember;
+ } catch (final NamingException e) {
+ throw new DirectoryException("Unable to determine mapping for member", e);
+ }
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#cascadePostSave(org.openthinclient.common.directory.Object)
+ */
+ @Override
+ protected void cascadePostSave(Object o, Transaction tx, DirContext ctx)
+ throws DirectoryException {
+ Set memberSet = (Set) getValue(o);
+ if (null == memberSet)
+ memberSet = EMPTY; // empty set
+
+ // if we still see the unchanged proxy, we're done!
+ if (!Proxy.isProxyClass(memberSet.getClass()))
+ for (final Object member : memberSet) {
+ final TypeMapping mm = getMappingForMember(member);
+
+ if (null == mm)
+ throw new DirectoryException(this
+ + ": set contains member of unmapped type: " + member.getClass());
+
+ mm.save(member, null, tx);
+ }
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#checkNull(javax.naming.directory.Attributes)
+ */
+ @Override
+ protected boolean checkNull(Attributes a) {
+ return false;
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#initNewInstance(org.openthinclient.common.directory.Object)
+ */
+ @Override
+ protected void initNewInstance(Object instance) throws DirectoryException {
+ // set new empty collection
+ setValue(instance, EMPTY);
+ }
+
+ /*
+ * @see org.openthinclient.common.directory.ldap.AttributeMapping#initPostLoad()
+ */
+ @Override
+ protected void initPostLoad() {
+ super.initPostLoad();
+
+ // we don't set up the member mapping, if this group accepts any kind of
+ // member
+ if (!memberType.equals(Object.class)) {
+ final TypeMapping member = type.getMapping().getMapping(memberType);
+ if (null == member)
+ throw new IllegalStateException(this + ": no mapping for member type "
+ + memberType);
+
+ this.memberMapping = member;
+
+ member.addReferrer(this);
+ }
+ }
+
+ @Override
+ public void setCardinality(String c) {
+ super.setCardinality(c);
+
+ if (cardinality != Cardinality.MANY
+ && cardinality != Cardinality.ONE_OR_MANY)
+ throw new IllegalArgumentException("Illegal cardinality " + cardinality
+ + " for " + type.getMappedType() + "." + fieldName);
+ }
+
+ @Override
+ Cardinality getCardinality() {
+ return cardinality;
+ }
+}
Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/OneToManyMapping.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RDNAttributeMapping.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RDNAttributeMapping.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RDNAttributeMapping.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RDNAttributeMapping.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.directory.odm;
+
+import java.util.regex.Pattern;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+
+/**
+ * A special mapping for the RDN attribute which quotes/unquotes the name in
+ * order to satisfy LDAP name escaping rules.
+ *
+ * @author levigo
+ */
+public class RDNAttributeMapping extends AttributeMapping {
+
+ /**
+ * @param fieldName
+ * @param fieldType
+ * @throws ClassNotFoundException
+ */
+ public RDNAttributeMapping(String fieldName)
+ throws ClassNotFoundException {
+ super(fieldName, "java.lang.String");
+ }
+
+ private static final Pattern QUOTE_TO_LDAP = Pattern.compile("[\\\\,=]");
+ private static final String QUOTE_REPLACEMENT = "\\\\$0";
+
+ /*
+ * @see org.apache.directory.odm.AttributeMapping#valueToAttributes(javax.naming.directory.BasicAttributes,
+ * java.lang.Object)
+ */
+ @Override
+ protected Object valueToAttributes(BasicAttributes a, Object v) {
+ assert v instanceof String;
+ return super.valueToAttributes(a, QUOTE_TO_LDAP.matcher((String) v)
+ .replaceAll(QUOTE_REPLACEMENT));
+ }
+
+ /** The Pattern used to un-quote a value from ldap escaping */
+ private static final Pattern UNQUOTE_FROM_LDAP = Pattern.compile("\\",
+ Pattern.LITERAL);
+
+ /*
+ * @see org.apache.directory.odm.AttributeMapping#valueFromAttributes(javax.naming.directory.Attributes,
+ * java.lang.Object)
+ */
+ @Override
+ protected Object valueFromAttributes(Attributes a, Object o, Transaction tx)
+ throws NamingException, DirectoryException {
+ Object value = super.valueFromAttributes(a, o, tx);
+ if (value instanceof String)
+ return UNQUOTE_FROM_LDAP.matcher((String) value).replaceAll("");
+ else
+ return value;
+ }
+}
Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RDNAttributeMapping.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ReferenceAttributeMapping.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ReferenceAttributeMapping.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ReferenceAttributeMapping.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ReferenceAttributeMapping.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.directory.odm;
+
+/**
+ * Abstract superclass for {@link AttributeMapping}s dealing with attributes
+ * which reference objects.
+ */
+abstract class ReferenceAttributeMapping extends AttributeMapping {
+ public ReferenceAttributeMapping(String fieldName, String fieldType)
+ throws ClassNotFoundException {
+ super(fieldName, fieldType);
+ }
+
+ abstract Cardinality getCardinality();
+}
Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/ReferenceAttributeMapping.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RollbackAction.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RollbackAction.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RollbackAction.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RollbackAction.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.directory.odm;
+
+/**
+ * @author levigo
+ */
+public interface RollbackAction {
+ public void performRollback() throws DirectoryException;
+}
Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RollbackAction.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RollbackException.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RollbackException.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RollbackException.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RollbackException.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.directory.odm;
+
+/**
+ * @author levigo
+ */
+public class RollbackException extends DirectoryException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public RollbackException(Throwable cause) {
+ super(
+ "Can't roll back transaction. The directory may be in an inconsistent state now.",
+ cause);
+ }
+}
Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/RollbackException.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/SecondLevelCache.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/SecondLevelCache.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/SecondLevelCache.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/SecondLevelCache.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.directory.odm;
+
+import java.io.IOException;
+
+import javax.naming.Name;
+import javax.naming.directory.Attributes;
+
+public interface SecondLevelCache {
+
+ /**
+ * Get the cache entry associated with the given Name.
+ *
+ * @param name
+ * @return
+ */
+ public abstract Attributes getEntry(Name name);
+
+ /**
+ * Purge the cache entry associated with the given name.
+ *
+ * @param name
+ * @return
+ * @throws IllegalStateException
+ */
+ public abstract boolean purgeEntry(Name name) throws IllegalStateException;
+
+ /**
+ * Store a cache entry for the given name.
+ *
+ * @param name
+ * @param object
+ */
+ public abstract void putEntry(Name name, Attributes a);
+
+ public abstract void clear() throws IllegalStateException, IOException;
+
+}
\ No newline at end of file
Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/SecondLevelCache.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Transaction.java
URL: http://svn.apache.org/viewvc/directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Transaction.java?rev=606228&view=auto
==============================================================================
--- directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Transaction.java (added)
+++ directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Transaction.java Fri Dec 21 08:03:46 2007
@@ -0,0 +1,367 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *******************************************************************************/
+package org.apache.directory.odm;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class models an LDAP transaction. Right now it serves the following
+ * purposes:
+ * <ul>
+ * <li>To detect cycles during cascading operations and
+ * <li>To handle the rollback of failed transactions.
+ * <li>To act as a transaction-scoped cache
+ * </ul>
+ * The latter is necessary since LDAP doesn't support atomic transactions
+ * spanning several entities. In order to perform a rollback, the system has to
+ * issue compensating actions in reverse order.
+ *
+ * @author levigo
+ */
+public class Transaction {
+ /**
+ * Special attribute ID used to store the a {@link TypeMapping}'s hash code
+ * in a cache element's attributes for later retrieval.
+ */
+ private static final String TYPE_MAPPING_KEY = "####TypeMappingKey####";
+
+ private static final Logger logger = LoggerFactory.getLogger(Transaction.class);
+
+ /**
+ * Set of processed entities during a cascading operation. Used to detect
+ * cycles.
+ */
+ private final Set processedEntities = new HashSet();
+
+ /**
+ * A list of actions to perform in order to roll back a transaction.
+ */
+ private final List<RollbackAction> rollbackActions = new LinkedList<RollbackAction>();
+
+ private final Map<Name, Object> cache = new HashMap<Name, Object>();
+
+ /**
+ * The Mapping that initiated this transaction.
+ */
+ private final Mapping mapping;
+
+ /**
+ * The Contexts opened by this transaction.
+ */
+ private final Map<DirectoryFacade, DirContext> contextCache = new HashMap<DirectoryFacade, DirContext>();
+
+ private final boolean disableGlobalCache;
+
+ boolean isClosed = false;
+
+ public Transaction(Mapping mapping) {
+ this(mapping, false);
+ }
+
+ public Transaction(Mapping mapping, boolean disableGlobalCache) {
+ this.mapping = mapping;
+ this.disableGlobalCache = disableGlobalCache;
+ }
+
+ /**
+ * Copy constructor. Copies just the ContextFactory from the other
+ * transaction.
+ *
+ * @param tx
+ */
+ public Transaction(Transaction tx) {
+ this(tx.mapping, tx.disableGlobalCache);
+ }
+
+ /**
+ * Add an entity which has been processed (saved/updated) during the
+ * transaction.
+ *
+ * @param entity
+ */
+ public void addEntity(Object entity) {
+ assertNotClosed();
+
+ processedEntities.add(entity);
+ }
+
+ /**
+ * Returns whether the given entity has already been processed during the
+ * transaction.
+ *
+ * @param entity
+ * @return
+ */
+ public boolean didAlreadyProcessEntity(Object entity) {
+ assertNotClosed();
+
+ return processedEntities.contains(entity);
+ }
+
+ /**
+ * Add an action to perform during rollback.
+ *
+ * @param action
+ */
+ public void addRollbackAction(RollbackAction action) {
+ rollbackActions.add(action);
+ }
+
+ /**
+ * Roll back the transaction by applying all RollbackActions in reverse order.
+ * If one of the actions fail, the rollback continues to undo as much work as
+ * possible.
+ */
+ public void rollback() throws RollbackException {
+ assertNotClosed();
+
+ try {
+ if (logger.isDebugEnabled())
+ logger.debug("ROLLBACK: Need to apply " + rollbackActions.size()
+ + " RollbackActions.");
+
+ final ListIterator<RollbackAction> i = rollbackActions
+ .listIterator(rollbackActions.size());
+ Throwable firstCause = null;
+ while (i.hasPrevious()) {
+ try {
+ i.previous().performRollback();
+ } catch (final Throwable e) {
+ if (null != firstCause)
+ firstCause = e;
+ logger
+ .error(
+ "Exception during Rollback. Trying to continue with rollback anyway.",
+ e);
+ }
+
+ if (null != firstCause)
+ throw new RollbackException(firstCause);
+ }
+ } finally {
+ try {
+ closeContexts();
+ } catch (final NamingException e) {
+ logger.error("Exception during commit - rolling back", e);
+ }
+ }
+ }
+
+ /**
+ * Get an entry from the cache.
+ *
+ * @param name
+ * @return
+ * @throws Exception
+ */
+ public Object getCacheEntry(Name name) throws Exception {
+ assertNotClosed();
+
+ final Object cached = cache.get(name);
+
+ if (null != cached) {
+ if (logger.isDebugEnabled())
+ logger.debug("TX cache hit for " + name);
+ return cached;
+ }
+
+ if (!disableGlobalCache) {
+ // got it in the second level cache?
+ final SecondLevelCache slc = mapping.getSecondLevelCache();
+ if (null != slc) {
+ final Attributes cachedAttributes = slc.getEntry(name);
+ if (null != cachedAttributes) {
+ if (logger.isDebugEnabled())
+ logger.debug("Global cache hit for " + name);
+
+ // re-create a new object instance from the cached attributes.
+ final Attribute a = cachedAttributes.get(TYPE_MAPPING_KEY);
+ if (null == a)
+ // should not happen
+ logger.error("No type mapping key in cached attributes");
+ else {
+ final int hashCode = ((Integer) a.get()).intValue();
+ cachedAttributes.remove(TYPE_MAPPING_KEY);
+
+ // find type mapping. FIXME: we may want to get rid of the linear
+ // search
+ for (final TypeMapping m : mapping.getMappers())
+ if (hashCode == m.hashCode()) {
+ // resurrect instance from attributes
+ final Object instance = m.createInstanceFromAttributes(name
+ .toString(), cachedAttributes, this);
+
+ // tx didn't have it yet!
+ cache.put(name, instance);
+
+ return instance;
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private void assertNotClosed() {
+ if (isClosed)
+ throw new IllegalStateException("Transaction already closed");
+ }
+
+ /**
+ * Put an entry into the cache. This method updates the first-level
+ * (transaction-scoped) cache as well as the second-level (mapping-scoped)
+ * cache.
+ *
+ * @param m TODO
+ * @param name
+ * @param value
+ */
+ public void putCacheEntry(TypeMapping m, Name name, Object value, Attributes a) {
+ assertNotClosed();
+
+ cache.put(name, value);
+
+ final SecondLevelCache slc = mapping.getSecondLevelCache();
+ if (null != slc) {
+ a.put(TYPE_MAPPING_KEY, m.hashCode());
+ slc.putEntry(name, a);
+ }
+ }
+
+ /**
+ * @throws RollbackException
+ *
+ */
+ public void commit() throws RollbackException {
+ assertNotClosed();
+
+ try {
+ closeContexts();
+ } catch (final NamingException e) {
+ logger.error("Exception during commit - rolling back", e);
+ rollback();
+ }
+ }
+
+ /**
+ * @throws NamingException
+ *
+ */
+ private void closeContexts() throws NamingException {
+ if (contextCache.size() == 0)
+ logger.debug("Closed without having opened a Context");
+
+ for (final DirContext ctx : contextCache.values())
+ ctx.close();
+ contextCache.clear();
+
+ isClosed = true;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (contextCache.size() > 0) {
+ logger.error("Internal error: disposed incompletely closed Transaction");
+
+ // clean up.
+ closeContexts();
+ }
+
+ super.finalize();
+ }
+
+ /**
+ * @param name
+ */
+ public void purgeCacheEntry(Name name) {
+ assertNotClosed();
+
+ cache.remove(name);
+
+ final SecondLevelCache slc = mapping.getSecondLevelCache();
+ if (null != slc)
+ slc.purgeEntry(name);
+ }
+
+ public DirContext getContext(DirectoryFacade connectionDescriptor)
+ throws DirectoryException {
+ assertNotClosed();
+
+ DirContext ctx = contextCache.get(connectionDescriptor);
+ if (null == ctx)
+ try {
+ ctx = openContext(connectionDescriptor);
+ contextCache.put(connectionDescriptor, ctx);
+ logger.debug("Created a Context for " + connectionDescriptor);
+ } catch (final NamingException e) {
+ throw new DirectoryException("Can't open connection", e);
+ }
+ return ctx;
+ }
+
+ private DirContext openContext(DirectoryFacade connectionDescriptor)
+ throws NamingException {
+ final DirContext ctx = connectionDescriptor.createDirContext();
+
+ if (connectionDescriptor.getLDAPEnv().get(
+ Mapping.PROPERTY_FORCE_SINGLE_THREADED) != null)
+ // Construct a dynamic proxy which forces all calls to the
+ // context
+ // to happen in a globally synchronized fashion.
+ return (DirContext) Proxy.newProxyInstance(getClass().getClassLoader(),
+ new Class[]{DirContext.class}, new InvocationHandler() {
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ synchronized (Mapping.class) { // sync globally
+ try {
+ return method.invoke(ctx, args);
+ } catch (final Exception e) {
+ throw e.getCause();
+ }
+ }
+ };
+ });
+
+ return ctx;
+ }
+
+ public boolean isClosed() {
+ return isClosed;
+ }
+}
Propchange: directory/sandbox/hennejg/odm/trunk/src/main/java/org/apache/directory/odm/Transaction.java
------------------------------------------------------------------------------
svn:mime-type = text/plain