001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 */
019package org.nuxeo.runtime.model;
020
021import java.util.HashMap;
022import java.util.Map;
023
024/**
025 * This is a contribution registry that is managing contribution fragments and merge them as needed. The implementation
026 * will be notified through {@link #contributionUpdated(String, Object)} each time you need to store or remove a
027 * contribution. Note that contribution objects that are registered by your implementation <b>must</b> not be modified.
028 * You can see them as immutable objects - otherwise your local changes will be lost at the next update event.
029 * <p>
030 * To use it you should extends this abstract implementation and implement the abstract methods.
031 * <p>
032 * The implementation registry doesn't need to be thread safe since it will be called from synchronized methods.
033 * <p>
034 * Also, the contribution object
035 * <p>
036 * A simple implementation is:
037 *
038 * <pre>
039 * public class MyRegistry extends ContributionFragmentRegistry&lt;MyContribution&gt; {
040 *     public Map&lt;String, MyContribution&gt; registry = new HAshMap&lt;String, MyContribution&gt;();
041 *
042 *     public String getContributionId(MyContribution contrib) {
043 *         return contrib.getId();
044 *     }
045 *
046 *     public void contributionUpdated(String id, MyContribution contrib, MyContribution origContrib) {
047 *         registry.put(id, contrib);
048 *     }
049 *
050 *     public void contributionRemoved(String id, MyContribution origContrib) {
051 *         registry.remove(id);
052 *     }
053 *
054 *     public MyContribution clone(MyContribution contrib) {
055 *          MyContribution clone = new MyContribution(contrib.getId());
056 *          clone.setSomeProperty(contrib.getSomeProperty());
057 *          ...
058 *          return clone;
059 *       }
060 *
061 *     public void merge(MyContribution src, MyContribution dst) {
062 *          dst.setSomeProperty(src.getSomeProperty());
063 *          ...
064 *       }
065 * }
066 * </pre>
067 *
068 * Since 5.5, if the registry does not support merging of resources, you can just override the method
069 * {@link #isSupportingMerge()} and return false, so that {@link #merge(Object, Object)} and {@link #clone()} are never
070 * called.
071 *
072 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
073 * @see SimpleContributionRegistry<T>
074 */
075public abstract class ContributionFragmentRegistry<T> {
076
077    protected Map<String, FragmentList<T>> contribs = new HashMap<String, FragmentList<T>>();
078
079    /**
080     * Returns the contribution ID given the contribution object
081     *
082     * @param contrib
083     * @return
084     */
085    public abstract String getContributionId(T contrib);
086
087    /**
088     * Adds or updates a contribution.
089     * <p>
090     * If the contribution doesn't yet exists then it will be added, otherwise the value will be updated. If the given
091     * value is null the existing contribution must be removed.
092     * <p>
093     * The second parameter is the contribution that should be updated when merging, as well as stored and used. This
094     * usually represents a clone of the original contribution or a merge of multiple contribution fragments.
095     * Modifications on this object at application level will be lost on next
096     * {@link #contributionUpdated(String, Object, Object)} call on the same object id: modifications should be done in
097     * the {@link #merge(Object, Object)} method.
098     * <p>
099     * The last parameter is the new contribution object, unchanged (original) which was neither cloned nor merged. This
100     * object should never be modified at application level, because it will be used each time a subsequent merge is
101     * done. Also, it never should be stored.
102     *
103     * @param id - the id of the contribution that needs to be updated
104     * @param contrib the updated contribution object that
105     * @param newOrigContrib - the new, unchanged (original) contribution fragment that triggered the update.
106     */
107    public abstract void contributionUpdated(String id, T contrib, T newOrigContrib);
108
109    /**
110     * All the fragments in the contribution was removed. Contribution must be unregistered.
111     * <p>
112     * The first parameter is the contribution ID that should be remove and the second parameter the original
113     * contribution fragment that as unregistered causing the contribution to be removed.
114     *
115     * @param id
116     * @param origContrib
117     */
118    public abstract void contributionRemoved(String id, T origContrib);
119
120    /**
121     * CLone the given contribution object
122     *
123     * @param object
124     * @return
125     */
126    public abstract T clone(T orig);
127
128    /**
129     * Merge 'src' into 'dst'. When merging only the 'dst' object is modified.
130     *
131     * @param src the object to copy over the 'dst' object
132     * @param dst this object is modified
133     */
134    public abstract void merge(T src, T dst);
135
136    /**
137     * Returns true if merge is supported.
138     * <p>
139     * Hook method to be overridden if merge logics behind {@link #clone()} and {@link #merge(Object, Object)} cannot be
140     * implemented.
141     *
142     * @since 5.5
143     */
144    public boolean isSupportingMerge() {
145        return true;
146    }
147
148    /**
149     * Add a new contribution. This will start install the new contribution and will notify the implementation about the
150     * value to add. (the final value to add may not be the same object as the one added - but a merge between multiple
151     * contributions)
152     *
153     * @param contrib
154     */
155    public synchronized void addContribution(T contrib) {
156        String id = getContributionId(contrib);
157        FragmentList<T> head = addFragment(id, contrib);
158        contributionUpdated(id, head.merge(this), contrib);
159    }
160
161    /**
162     * Remove a contribution. This will uninstall the contribution and notify the implementation about the new value it
163     * should store (after re-merging contribution fragments).
164     * <p>
165     * Uses standard equality to check for old objects (useEqualsMethod == false).
166     *
167     * @param contrib
168     * @see #removeContribution(Object, boolean)
169     */
170    public synchronized void removeContribution(T contrib) {
171        removeContribution(contrib, false);
172    }
173
174    /**
175     * Remove a contribution. This will uninstall the contribution and notify the implementation about the new value it
176     * should store (after re-merging contribution fragments).
177     * <p>
178     * Equality can be controlled from here.
179     * <p>
180     * Contributions come from the runtime that keeps exact instances, so using equality usually makes it possible to
181     * remove the exact instance that was contributed by this component (without needing to reference the component name
182     * for instance). But when unit-testing, or when registrating contributions that do not come directly from the
183     * runtime, regirties need to use the equals method defined on each contribution.
184     *
185     * @param contrib the contrib to remove
186     * @param useEqualsMethod a boolean stating that old contributions should be checked using the equals method instead
187     *            of
188     * @since 5.6
189     */
190    public synchronized void removeContribution(T contrib, boolean useEqualsMethod) {
191        String id = getContributionId(contrib);
192        FragmentList<T> head = removeFragment(id, contrib, useEqualsMethod);
193        if (head != null) {
194            T result = head.merge(this);
195            if (result != null) {
196                contributionUpdated(id, result, contrib);
197            } else {
198                contributionRemoved(id, contrib);
199            }
200        }
201    }
202
203    /**
204     * Get a merged contribution directly from the internal registry - and avoid passing by the implementation registry.
205     * Note that this operation will invoke a merge of existing fragments if needed.
206     * <p>
207     * Since 5.5, this method has made protected as it should not be used by the service retrieving merged resources
208     * (otherwise merge will be done again). If you'd really like to call it, add a public method on your registry
209     * implementation that will call it.
210     *
211     * @param id
212     * @return
213     */
214    protected synchronized T getContribution(String id) {
215        FragmentList<T> head = contribs.get(id);
216        return head != null ? head.merge(this) : null;
217    }
218
219    /**
220     * Get an array of all contribution fragments
221     *
222     * @return
223     */
224    @SuppressWarnings("unchecked")
225    public synchronized FragmentList<T>[] getFragments() {
226        return contribs.values().toArray(new FragmentList[contribs.size()]);
227    }
228
229    protected FragmentList<T> addFragment(String id, T contrib) {
230        FragmentList<T> head = contribs.get(id);
231        if (head == null) {
232            // no merge needed
233            head = new FragmentList<T>();
234            this.contribs.put(id, head);
235        }
236        head.add(contrib);
237        return head;
238    }
239
240    protected FragmentList<T> removeFragment(String id, T contrib, boolean useEqualsMethod) {
241        FragmentList<T> head = contribs.get(id);
242        if (head == null) {
243            return null;
244        }
245        if (head.remove(contrib, useEqualsMethod)) {
246            if (head.isEmpty()) {
247                contribs.remove(id);
248            }
249            return head;
250        }
251        return null;
252    }
253
254    public static class FragmentList<T> extends Fragment<T> {
255
256        public FragmentList() {
257            super(null);
258            prev = this;
259            next = this;
260        }
261
262        public boolean isEmpty() {
263            return next == null;
264        }
265
266        public T merge(ContributionFragmentRegistry<T> reg) {
267            T mergedValue = object;
268            if (mergedValue != null) {
269                return mergedValue;
270            }
271            Fragment<T> p = next;
272            if (p == this) {
273                return null;
274            }
275            mergedValue = reg.isSupportingMerge() ? reg.clone(p.object) : p.object;
276            p = p.next;
277            while (p != this) {
278                if (reg.isSupportingMerge()) {
279                    reg.merge(p.object, mergedValue);
280                } else {
281                    mergedValue = p.object;
282                }
283                p = p.next;
284            }
285            object = mergedValue;
286            return mergedValue;
287        }
288
289        public final void add(T contrib) {
290            insertBefore(new Fragment<T>(contrib));
291            object = null;
292        }
293
294        public final void add(Fragment<T> fragment) {
295            insertBefore(fragment);
296            object = null;
297        }
298
299        public boolean remove(T contrib) {
300            return remove(contrib, false);
301        }
302
303        /**
304         * @since 5.6
305         */
306        public boolean remove(T contrib, boolean useEqualsMethod) {
307            Fragment<T> p = next;
308            while (p != this) {
309                if ((useEqualsMethod && (p.object != null && p.object.equals(contrib)))
310                        || (!useEqualsMethod && p.object == contrib)) {
311                    p.remove();
312                    object = null;
313                    return true;
314                }
315                p = p.next;
316            }
317            return false;
318        }
319    }
320
321    public static class Fragment<T> {
322        public T object;
323
324        public Fragment<T> next;
325
326        public Fragment<T> prev;
327
328        public Fragment(T object) {
329            this.object = object;
330        }
331
332        public final void insertBefore(Fragment<T> fragment) {
333            fragment.prev = prev;
334            fragment.next = this;
335            prev.next = fragment;
336            prev = fragment;
337        }
338
339        public final void insertAfter(Fragment<T> fragment) {
340            fragment.prev = this;
341            fragment.next = next;
342            next.prev = fragment;
343            next = fragment;
344        }
345
346        public final void remove() {
347            prev.next = next;
348            next.prev = prev;
349            next = prev = null;
350        }
351
352        public final boolean hasNext() {
353            return next != null;
354        }
355
356        public final boolean hasPrev() {
357            return prev != null;
358        }
359    }
360
361}