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 */
017package org.apache.xbean.naming.context;
018
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.Map;
022import java.util.Hashtable;
023import java.util.concurrent.atomic.AtomicReference;
024import java.util.concurrent.locks.Lock;
025import java.util.concurrent.locks.ReentrantLock;
026
027import javax.naming.Context;
028import javax.naming.ContextNotEmptyException;
029import javax.naming.NameAlreadyBoundException;
030import javax.naming.NamingException;
031import javax.naming.Referenceable;
032import javax.naming.Reference;
033import javax.naming.spi.NamingManager;
034
035import org.apache.xbean.naming.reference.CachingReference;
036
037/**
038 * @version $Rev$ $Date$
039 */
040public 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}