001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.xbean.naming.context;
018    
019    import java.util.Collections;
020    import java.util.HashMap;
021    import java.util.Map;
022    import java.util.Hashtable;
023    import java.util.concurrent.atomic.AtomicReference;
024    import java.util.concurrent.locks.Lock;
025    import java.util.concurrent.locks.ReentrantLock;
026    
027    import javax.naming.Context;
028    import javax.naming.ContextNotEmptyException;
029    import javax.naming.NameAlreadyBoundException;
030    import javax.naming.NamingException;
031    import javax.naming.Referenceable;
032    import javax.naming.Reference;
033    import javax.naming.spi.NamingManager;
034    
035    import org.apache.xbean.naming.reference.CachingReference;
036    
037    /**
038     * @version $Rev$ $Date$
039     */
040    public class WritableContext extends AbstractFederatedContext {
041        private final Lock writeLock = new ReentrantLock();
042        private final AtomicReference<Map<String, Object>> bindingsRef;
043        private final AtomicReference<Map<String, Object>> indexRef;
044        private final boolean cacheReferences;
045        private final boolean supportReferenceable;
046        private final boolean checkDereferenceDifferent;
047        private final boolean assumeDereferenceBound;
048    
049        public WritableContext() throws NamingException {
050            this("", Collections.<String, Object>emptyMap(), ContextAccess.MODIFIABLE, false);
051        }
052    
053        public WritableContext(String nameInNamespace) throws NamingException {
054            this(nameInNamespace, Collections.<String, Object>emptyMap(), ContextAccess.MODIFIABLE, false);
055        }
056    
057        public WritableContext(String nameInNamespace, Map<String, Object> bindings) throws NamingException {
058            this(nameInNamespace, bindings, ContextAccess.MODIFIABLE, false);
059        }
060    
061        public WritableContext(String nameInNamespace, Map<String, Object> bindings, boolean cacheReferences) throws NamingException {
062            this(nameInNamespace, bindings, ContextAccess.MODIFIABLE, cacheReferences);
063        }
064    
065        public WritableContext(String nameInNamespace, Map<String, Object> bindings, ContextAccess contextAccess) throws NamingException {
066            this(nameInNamespace, bindings, contextAccess, false);
067        }
068    
069        public WritableContext(String nameInNamespace, Map<String, Object> bindings, ContextAccess contextAccess, boolean cacheReferences) throws NamingException {
070            this(nameInNamespace, bindings, contextAccess, cacheReferences, true, true, false);
071        }
072        public WritableContext(String nameInNamespace,
073                               Map<String, Object> bindings,
074                               ContextAccess contextAccess,
075                               boolean cacheReferences,
076                               boolean supportReferenceable,
077                               boolean checkDereferenceDifferent,
078                               boolean assumeDereferenceBound) throws NamingException {
079            super(nameInNamespace, contextAccess);
080    
081            this.cacheReferences = cacheReferences;
082            if (this.cacheReferences) {
083                bindings = CachingReference.wrapReferences(bindings, this);
084            }
085            this.supportReferenceable = supportReferenceable;
086            this.checkDereferenceDifferent = checkDereferenceDifferent;
087            this.assumeDereferenceBound = assumeDereferenceBound;
088    
089            Map<String, Object> localBindings = ContextUtil.createBindings(bindings, this);
090    
091            this.bindingsRef = new AtomicReference<Map<String, Object>>(Collections.unmodifiableMap(localBindings));
092            this.indexRef = new AtomicReference<Map<String, Object>>(Collections.unmodifiableMap(buildIndex("", localBindings)));
093        }
094    
095        protected boolean addBinding(String name, Object value, boolean rebind) throws NamingException {
096            if (super.addBinding(name, value, rebind)) {
097                return true;
098            }
099    
100            addBinding(bindingsRef, name, getNameInNamespace(name), value, rebind);
101            return true;
102        }
103    
104        protected void addBinding(AtomicReference<Map<String, Object>> bindingsRef, String name, String nameInNamespace, Object value, boolean rebind) throws NamingException {
105            if (supportReferenceable && value instanceof Referenceable) {
106                Reference ref = ((Referenceable)value).getReference();
107                if (ref != null) {
108                    if (checkDereferenceDifferent) {
109                        try {
110                            Object o = NamingManager.getObjectInstance(ref, null, null, new Hashtable());
111                            if (!value.equals(o)) {
112                                value = ref;
113                            }
114                        } catch (Exception e) {
115                            if (!assumeDereferenceBound) {
116                                value = ref;
117                            }
118                        }
119                    } else {
120                        value = ref;
121                    }
122                }
123    
124            }
125            if (cacheReferences) {
126                value = CachingReference.wrapReference(name, value, this);
127            }
128    
129            writeLock.lock();
130            try {
131                Map<String, Object> bindings = bindingsRef.get();
132    
133                if (!rebind && bindings.containsKey(name)) {
134                    throw new NameAlreadyBoundException(name);
135                }
136                Map<String, Object> newBindings = new HashMap<String, Object>(bindings);
137                newBindings.put(name,value);
138                bindingsRef.set(newBindings);
139    
140                addToIndex(nameInNamespace, value);
141            } finally {
142                writeLock.unlock();
143            }
144        }
145    
146        private void addToIndex(String name, Object value) {
147            Map<String, Object> index = indexRef.get();
148            Map<String, Object> newIndex = new HashMap<String, Object>(index);
149            newIndex.put(name, value);
150            if (value instanceof NestedWritableContext) {
151                NestedWritableContext nestedcontext = (NestedWritableContext) value;
152                Map<String, Object> newIndexValues = buildIndex(name, nestedcontext.bindingsRef.get());
153                newIndex.putAll(newIndexValues);
154            }
155            indexRef.set(newIndex);
156        }
157    
158        protected boolean removeBinding(String name, boolean removeNotEmptyContext) throws NamingException {
159            if (super.removeBinding(name, removeNotEmptyContext)) {
160                return true;
161            }
162            removeBinding(bindingsRef, name, getNameInNamespace(name), removeNotEmptyContext);
163            return true;
164        }
165    
166        private boolean removeBinding(AtomicReference<Map<String, Object>> bindingsRef, String name, String nameInNamespace, boolean removeNotEmptyContext) throws NamingException {
167            writeLock.lock();
168            try {
169                Map<String, Object> bindings = bindingsRef.get();
170                if (!bindings.containsKey(name)) {
171                    // remove is idempotent meaning remove succeededs even if there was no value bound
172                    return false;
173                }
174    
175                Map<String, Object> newBindings = new HashMap<String, Object>(bindings);
176                Object oldValue = newBindings.remove(name);
177                if (!removeNotEmptyContext && oldValue instanceof Context && !isEmpty((Context)oldValue)) {
178                    throw new ContextNotEmptyException(name);
179                }
180                bindingsRef.set(newBindings);
181    
182                Map<String, Object> newIndex = removeFromIndex(nameInNamespace);
183                indexRef.set(newIndex);
184                return true;
185            } finally {
186                writeLock.unlock();
187            }
188        }
189    
190        private Map<String, Object> removeFromIndex(String name) {
191            Map<String, Object> index = indexRef.get();
192            Map<String, Object> newIndex = new HashMap<String, Object>(index);
193            Object oldValue = newIndex.remove(name);
194            if (oldValue instanceof NestedWritableContext) {
195                NestedWritableContext nestedcontext = (NestedWritableContext) oldValue;
196                Map<String, Object> removedIndexValues = buildIndex(name, nestedcontext.bindingsRef.get());
197                for (String key : removedIndexValues.keySet()) {
198                    newIndex.remove(key);
199                }
200            }
201            return newIndex;
202        }
203    
204        public Context createNestedSubcontext(String path, Map<String, Object> bindings) throws NamingException {
205            if (getNameInNamespace().length() > 0) {
206                path = getNameInNamespace() + "/" + path;
207            }
208            return new NestedWritableContext(path, bindings);
209        }
210    
211        private static Map<String, Object> buildIndex(String nameInNamespace, Map<String, Object> bindings) {
212            String path = nameInNamespace;
213            if (path.length() > 0 && !path.endsWith("/")) {
214                path += "/";
215            }
216    
217            Map<String, Object> absoluteIndex = new HashMap<String, Object>();
218            for (Map.Entry<String, Object> entry : bindings.entrySet()) {
219                String name = entry.getKey();
220                Object value = entry.getValue();
221                if (value instanceof NestedWritableContext) {
222                    NestedWritableContext nestedContext = (NestedWritableContext) value;
223                    absoluteIndex.putAll(buildIndex(nestedContext.pathWithSlash, nestedContext.bindingsRef.get()));
224                }
225                absoluteIndex.put(path + name, value);
226            }
227            return absoluteIndex;
228        }
229    
230        protected Object getDeepBinding(String name) {
231            Map<String, Object> index = indexRef.get();
232            return index.get(name);
233        }
234    
235        protected Map<String, Object> getWrapperBindings() throws NamingException {
236            return bindingsRef.get();
237        }
238    
239        /**
240         * Nested context which shares the absolute index map in MapContext.
241         */
242        public class NestedWritableContext extends AbstractFederatedContext {
243            private final AtomicReference<Map<String, Object>> bindingsRef;
244            private final String pathWithSlash;
245    
246            public NestedWritableContext(String path, Map<String, Object> bindings) throws NamingException {
247                super(WritableContext.this, path);
248                
249                path = getNameInNamespace();
250                if (!path.endsWith("/")) path += "/";
251                this.pathWithSlash = path;
252    
253                this.bindingsRef = new AtomicReference<Map<String, Object>>(Collections.unmodifiableMap(bindings));
254            }
255    
256            public Context createNestedSubcontext(String path, Map<String, Object> bindings) throws NamingException {
257                return new NestedWritableContext(getNameInNamespace(path), bindings);
258            }
259    
260            protected Object getDeepBinding(String name) {
261                String absoluteName = pathWithSlash + name;
262                return WritableContext.this.getDeepBinding(absoluteName);
263            }
264    
265            protected Map<String, Object> getWrapperBindings() throws NamingException {
266                return bindingsRef.get();
267            }
268    
269            protected boolean addBinding(String name, Object value, boolean rebind) throws NamingException {
270                if (super.addBinding(name, value, rebind)) {
271                    return true;
272                }
273    
274                WritableContext.this.addBinding(bindingsRef, name, getNameInNamespace(name), value, rebind);
275                return true;
276            }
277    
278            protected boolean removeBinding(String name, boolean removeNotEmptyContext) throws NamingException {
279                if (WritableContext.this.removeBinding(bindingsRef, name, getNameInNamespace(name), removeNotEmptyContext)) {
280                    return true;
281                }
282                return super.removeBinding(name, false);
283            }
284        }
285    }