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.concurrent.atomic.AtomicReference;
020    import java.util.concurrent.locks.Lock;
021    import java.util.concurrent.locks.ReentrantLock;
022    import org.apache.xbean.naming.reference.CachingReference;
023    
024    import javax.naming.Context;
025    import javax.naming.NamingException;
026    import javax.naming.NameAlreadyBoundException;
027    import javax.naming.ContextNotEmptyException;
028    import java.util.Collections;
029    import java.util.HashMap;
030    import java.util.Iterator;
031    import java.util.Map;
032    
033    /**
034     * @version $Rev$ $Date$
035     */
036    public class WritableContext extends AbstractFederatedContext {
037        private final Lock writeLock = new ReentrantLock();
038        private final AtomicReference bindingsRef;
039        private final AtomicReference indexRef;
040        private final boolean cacheReferences;
041    
042        public WritableContext() throws NamingException {
043            this("", Collections.EMPTY_MAP, ContextAccess.MODIFIABLE, false);
044        }
045    
046        public WritableContext(String nameInNamespace) throws NamingException {
047            this(nameInNamespace, Collections.EMPTY_MAP, ContextAccess.MODIFIABLE, false);
048        }
049    
050        public WritableContext(String nameInNamespace, Map bindings) throws NamingException {
051            this(nameInNamespace, bindings, ContextAccess.MODIFIABLE, false);
052        }
053    
054        public WritableContext(String nameInNamespace, Map bindings, boolean cacheReferences) throws NamingException {
055            this(nameInNamespace, bindings, ContextAccess.MODIFIABLE, cacheReferences);
056        }
057    
058        public WritableContext(String nameInNamespace, Map bindings, ContextAccess contextAccess) throws NamingException {
059            this(nameInNamespace, bindings, contextAccess, false);
060        }
061    
062        public WritableContext(String nameInNamespace, Map bindings, ContextAccess contextAccess, boolean cacheReferences) throws NamingException {
063            super(nameInNamespace, contextAccess);
064    
065            this.cacheReferences = cacheReferences;
066            if (this.cacheReferences) {
067                bindings = CachingReference.wrapReferences(bindings);
068            }
069    
070            Map localBindings = ContextUtil.createBindings(bindings, this);
071    
072            this.bindingsRef = new AtomicReference(Collections.unmodifiableMap(localBindings));
073            this.indexRef = new AtomicReference(Collections.unmodifiableMap(buildIndex("", localBindings)));
074        }
075    
076        protected boolean addBinding(String name, Object value, boolean rebind) throws NamingException {
077            if (super.addBinding(name, value, rebind)) {
078                return true;
079            }
080    
081            addBinding(bindingsRef, name, getNameInNamespace(name), value, rebind);
082            return true;
083        }
084    
085        protected void addBinding(AtomicReference bindingsRef, String name, String nameInNamespace, Object value, boolean rebind) throws NamingException {
086            writeLock.lock();
087            try {
088                Map bindings = (Map) bindingsRef.get();
089    
090                if (!rebind && bindings.containsKey(name)) {
091                    throw new NameAlreadyBoundException(name);
092                }
093                if (cacheReferences) {
094                    value = CachingReference.wrapReference(getNameInNamespace(name), value);
095                }
096    
097                Map newBindings = new HashMap(bindings);
098                newBindings.put(name,value);
099                bindingsRef.set(newBindings);
100    
101                addToIndex(nameInNamespace, value);
102            } finally {
103                writeLock.unlock();
104            }
105        }
106    
107        private void addToIndex(String name, Object value) {
108            Map index = (Map) indexRef.get();
109            Map newIndex = new HashMap(index);
110            newIndex.put(name, value);
111            if (value instanceof NestedWritableContext) {
112                NestedWritableContext nestedcontext = (NestedWritableContext) value;
113                Map newIndexValues = buildIndex(name, (Map) nestedcontext.bindingsRef.get());
114                newIndex.putAll(newIndexValues);
115            }
116            indexRef.set(newIndex);
117        }
118    
119        protected boolean removeBinding(String name, boolean removeNotEmptyContext) throws NamingException {
120            if (super.removeBinding(name, removeNotEmptyContext)) {
121                return true;
122            }
123            removeBinding(bindingsRef, name, removeNotEmptyContext);
124            return true;
125        }
126    
127        private boolean removeBinding(AtomicReference bindingsRef, String name, boolean removeNotEmptyContext) throws NamingException {
128            writeLock.lock();
129            try {
130                Map bindings = (Map) bindingsRef.get();
131                if (!bindings.containsKey(name)) {
132                    // remove is idempotent meaning remove succeededs even if there was no value bound
133                    return false;
134                }
135    
136                Map newBindings = new HashMap(bindings);
137                Object oldValue = newBindings.remove(name);
138                if (!removeNotEmptyContext && oldValue instanceof Context && !isEmpty((Context)oldValue)) {
139                    throw new ContextNotEmptyException(name);
140                }
141                bindingsRef.set(newBindings);
142    
143                Map newIndex = removeFromIndex(name);
144                indexRef.set(newIndex);
145                return true;
146            } finally {
147                writeLock.unlock();
148            }
149        }
150    
151        private Map removeFromIndex(String name) {
152            Map index = (Map) indexRef.get();
153            Map newIndex = new HashMap(index);
154            Object oldValue = newIndex.remove(name);
155            if (oldValue instanceof NestedWritableContext) {
156                NestedWritableContext nestedcontext = (NestedWritableContext) oldValue;
157                Map removedIndexValues = buildIndex(name, (Map) nestedcontext.bindingsRef.get());
158                for (Iterator iterator = removedIndexValues.keySet().iterator(); iterator.hasNext();) {
159                    String key = (String) iterator.next();
160                    newIndex.remove(key);
161                }
162            }
163            return newIndex;
164        }
165    
166        public Context createNestedSubcontext(String path, Map bindings) throws NamingException {
167            return new NestedWritableContext(path,bindings);
168        }
169    
170        private static Map buildIndex(String nameInNamespace, Map bindings) {
171            String path = nameInNamespace;
172            if (path.length() > 0 && !path.endsWith("/")) {
173                path += "/";
174            }
175    
176            Map absoluteIndex = new HashMap();
177            for (Iterator iterator = bindings.entrySet().iterator(); iterator.hasNext();) {
178                Map.Entry entry = (Map.Entry) iterator.next();
179                String name = (String) entry.getKey();
180                Object value = entry.getValue();
181                if (value instanceof NestedWritableContext) {
182                    NestedWritableContext nestedContext = (NestedWritableContext)value;
183                    absoluteIndex.putAll(buildIndex(nestedContext.pathWithSlash, (Map) nestedContext.bindingsRef.get()));
184                }
185                absoluteIndex.put(path + name, value);
186            }
187            return absoluteIndex;
188        }
189    
190        protected Object getDeepBinding(String name) {
191            Map index = (Map) indexRef.get();
192            return index.get(name);
193        }
194    
195        protected Map getWrapperBindings() throws NamingException {
196            Map bindings = (Map) bindingsRef.get();
197            return bindings;
198        }
199    
200        /**
201         * Nested context which shares the absolute index map in MapContext.
202         */
203        public class NestedWritableContext extends AbstractFederatedContext {
204            private final AtomicReference bindingsRef;
205            private final String pathWithSlash;
206    
207            public NestedWritableContext(String path, Map bindings) throws NamingException {
208                super(WritableContext.this, path);
209    
210                if (!path.endsWith("/")) path += "/";
211                this.pathWithSlash = path;
212    
213                this.bindingsRef = new AtomicReference(Collections.unmodifiableMap(bindings));
214            }
215    
216            public Context createNestedSubcontext(String path, Map bindings) throws NamingException {
217                return new NestedWritableContext(path, bindings);
218            }
219    
220            protected Object getDeepBinding(String name) {
221                String absoluteName = pathWithSlash + name;
222                return WritableContext.this.getDeepBinding(absoluteName);
223            }
224    
225            protected Map getWrapperBindings() throws NamingException {
226                Map bindings = (Map) bindingsRef.get();
227                return bindings;
228            }
229    
230            protected boolean addBinding(String name, Object value, boolean rebind) throws NamingException {
231                if (super.addBinding(name, value, rebind)) {
232                    return true;
233                }
234    
235                WritableContext.this.addBinding(bindingsRef, name, getNameInNamespace(name), value, rebind);
236                return true;
237            }
238    
239            protected boolean removeBinding(String name, boolean removeNotEmptyContext) throws NamingException {
240                if (WritableContext.this.removeBinding(bindingsRef, name, false)) {
241                    return true;
242                }
243                return super.removeBinding(name, false);
244            }
245        }
246    }