001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.hivemind.impl;
016    
017    import java.util.ArrayList;
018    import java.util.Collections;
019    import java.util.List;
020    import java.util.Map;
021    
022    import org.apache.commons.logging.Log;
023    import org.apache.commons.logging.LogFactory;
024    import org.apache.hivemind.*;
025    import org.apache.hivemind.ApplicationRuntimeException;
026    import org.apache.hivemind.Occurances;
027    import org.apache.hivemind.internal.ConfigurationPoint;
028    import org.apache.hivemind.internal.Contribution;
029    import org.apache.hivemind.schema.Schema;
030    import org.apache.hivemind.util.ToStringBuilder;
031    
032    /**
033     * Implementation of the {@link org.apache.hivemind.internal.ConfigurationPoint} interface; a
034     * container for {@link org.apache.hivemind.internal.Contribution}s.
035     * 
036     * @author Howard Lewis Ship
037     */
038    public final class ConfigurationPointImpl extends AbstractExtensionPoint implements
039            ConfigurationPoint
040    {
041        private static final Log LOG = LogFactory.getLog(ConfigurationPointImpl.class);
042    
043        /**
044         * The cached elements for the extension point (if caching is enabled).
045         */
046        private List _elements;
047    
048        private List _elementsProxy;
049    
050        private Map _mappedElements;
051    
052        private Map _mappedElementsProxy;
053    
054        private boolean _canElementsBeMapped = false;
055    
056        private Occurances _expectedCount;
057    
058        private List _contributions;
059    
060        private boolean _building;
061    
062        private Schema _contributionsSchema;
063    
064        private ShutdownCoordinator _shutdownCoordinator;
065    
066        protected void extendDescription(ToStringBuilder builder)
067        {
068            builder.append("expectedCount", _expectedCount);
069            builder.append("contributions", _contributions);
070            builder.append("schema", _contributionsSchema);
071        }
072    
073        /**
074         * Returns the number of contributions; it is expected that each top-level
075         * {@link org.apache.hivemind.Element} in each {@link Contribution} will convert to one element
076         * instance; the value returned is the total number of top-level elements in all contributed
077         * Extensions.
078         */
079        public int getContributionCount()
080        {
081            if (_contributions == null)
082                return 0;
083    
084            int total = 0;
085    
086            int count = _contributions.size();
087            for (int i = 0; i < count; i++)
088            {
089                Contribution c = (Contribution) _contributions.get(i);
090                total += c.getElements().size();
091            }
092    
093            return total;
094        }
095    
096        public void addContribution(Contribution c)
097        {
098            if (_contributions == null)
099                _contributions = new ArrayList();
100    
101            _contributions.add(c);
102        }
103    
104        public Occurances getExpectedCount()
105        {
106            return _expectedCount;
107        }
108    
109        public void setExpectedCount(Occurances occurances)
110        {
111            _expectedCount = occurances;
112        }
113    
114        /**
115         * Returns the contributed elements as an unmodifiable {@link List}. Internally, a proxy to the
116         * real list is returned, such that the real list may not be constructed until actually needed.
117         */
118        public synchronized List getElements()
119        {
120            if (_elements != null)
121                return _elements;
122    
123            if (_elementsProxy == null)
124            {
125                ElementsProxyList outerProxy = new ElementsProxyList();
126    
127                new ElementsInnerProxyList(this, outerProxy);
128    
129                _shutdownCoordinator.addRegistryShutdownListener(outerProxy);
130    
131                _elementsProxy = outerProxy;
132            }
133    
134            return _elementsProxy;
135        }
136    
137        public boolean areElementsMappable()
138        {
139            return _canElementsBeMapped;
140        }
141    
142        /**
143         * Returns the contributed elements as an unmodifiable {@link Map}. Internally, a proxy to the
144         * real map is returned, such that the real map may not be constructed until actually needed.
145         */
146        public synchronized Map getElementsAsMap()
147        {
148            if (!areElementsMappable())
149                throw new ApplicationRuntimeException(ImplMessages.unableToMapConfiguration(this));
150    
151            if (_mappedElements != null)
152                return _mappedElements;
153    
154            if (_mappedElementsProxy == null)
155            {
156                ElementsProxyMap outerProxy = new ElementsProxyMap();
157    
158                new ElementsInnerProxyMap(this, outerProxy);
159    
160                _shutdownCoordinator.addRegistryShutdownListener(outerProxy);
161    
162                _mappedElementsProxy = outerProxy;
163            }
164    
165            return _mappedElementsProxy;
166        }
167    
168        /**
169         * Invoked by {@link ElementsInnerProxyList} when the actual list is needed. Returns the List
170         * (which is modifiable, but that's OK because ElementsInnerProxyList is unmodifiable) created
171         * by calling {@link #processContributionElements()}.
172         */
173        synchronized List constructElements()
174        {
175            // It's nice to have this protection, but (unlike services), you
176            // would really have to go out of your way to provoke
177            // a recursive configuration.
178    
179            if (_building)
180                throw new ApplicationRuntimeException(ImplMessages
181                        .recursiveConfiguration(getExtensionPointId()));
182    
183            try
184            {
185                if (_elements == null)
186                {
187                    _building = true;
188    
189                    processContributionElements();
190                }
191    
192                // Now that we have the real list, we don't need the proxy anymore, either.
193    
194                _elementsProxy = null;
195    
196                return _elements;
197            }
198            finally
199            {
200                _building = false;
201            }
202        }
203    
204        /**
205         * Analoguously to {@link #constructElements()} this method will be called by
206         * {@link ElementsInnerProxyMap} to construct the actual map.
207         */
208        synchronized Map constructMapElements()
209        {
210            // It's nice to have this protection, but (unlike services), you
211            // would really have to go out of your way to provoke
212            // a recursive configuration.
213    
214            if (_building)
215                throw new ApplicationRuntimeException(ImplMessages
216                        .recursiveConfiguration(getExtensionPointId()));
217    
218            try
219            {
220                if (_mappedElements == null)
221                {
222                    _building = true;
223    
224                    processContributionElements();
225                }
226    
227                // Now that we have the real map, we don't need the proxy anymore, either.
228    
229                _mappedElementsProxy = null;
230    
231                return _mappedElements;
232            }
233            finally
234            {
235                _building = false;
236            }
237        }
238    
239        /**
240         * Processes the contribution elements using the
241         * {@link org.apache.hivemind.schema.SchemaProcessor}. The processed contributions will be
242         * stored as an immutable list (in {@link #_elements}) and as an immutable map (in
243         * {@link #_mappedElements}) if applicable (see {@link #areElementsMappable()}).
244         */
245        private void processContributionElements()
246        {
247            if (LOG.isDebugEnabled())
248                LOG.debug("Constructing extension point " + getExtensionPointId());
249    
250            if (_contributions == null)
251            {
252                _elements = Collections.EMPTY_LIST;
253                _mappedElements = Collections.EMPTY_MAP;
254    
255                return;
256            }
257    
258            SchemaProcessorImpl processor = new SchemaProcessorImpl(getErrorLog(), _contributionsSchema);
259    
260            int count = _contributions.size();
261    
262            try
263            {
264                for (int i = 0; i < count; i++)
265                {
266                    Contribution extension = (Contribution) _contributions.get(i);
267    
268                    processor.process(extension.getElements(), extension.getContributingModule());
269                }
270            }
271            catch (Exception ex)
272            {
273                throw new ApplicationRuntimeException(ImplMessages.unableToConstructConfiguration(
274                        getExtensionPointId(),
275                        ex), ex);
276            }
277    
278            if (areElementsMappable())
279                _mappedElements = Collections.unmodifiableMap(processor.getMappedElements());
280    
281            _elements = Collections.unmodifiableList(processor.getElements());
282    
283            // After constructing the result, if the result
284            // will be cached, then there's no need to keep
285            // the schema and extensions (used to build the
286            // result); it can all be released to the GC.
287    
288            _contributionsSchema = null;
289            _contributions = null;
290        }
291    
292        public Schema getSchema()
293        {
294            return _contributionsSchema;
295        }
296    
297        public void setContributionsSchema(Schema schema)
298        {
299            _contributionsSchema = schema;
300    
301            _canElementsBeMapped = _contributionsSchema != null
302                    && _contributionsSchema.canInstancesBeKeyed();
303        }
304    
305        public Schema getContributionsSchema()
306        {
307            return _contributionsSchema;
308        }
309    
310        public void setShutdownCoordinator(ShutdownCoordinator coordinator)
311        {
312            _shutdownCoordinator = coordinator;
313        }
314    
315    }