001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     bstefanescu
016 *
017 * $Id$
018 */
019
020package org.nuxeo.runtime.contribution.impl;
021
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027
028import org.nuxeo.runtime.contribution.Contribution;
029import org.nuxeo.runtime.contribution.ContributionRegistry;
030
031/**
032 * The parent provider is read only. It is never modified by the registry. It serves only to resolve dependencies. This
033 * allows greater flexibility in managing dependencies. This registry may have a parent registry that can be used only
034 * read only.
035 *
036 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
037 */
038// TODO need to implement a visibility (PRIVATE, PROTECTED, PUBLIC etc)
039// on contributions when extending other registries
040public abstract class AbstractContributionRegistry<K, T> implements ContributionRegistry<K, T> {
041
042    protected final Map<Object, Contribution<K, T>> registry;
043
044    protected final AbstractContributionRegistry<K, T> parent;
045
046    protected final List<AbstractContributionRegistry<K, T>> listeners;
047
048    protected AbstractContributionRegistry() {
049        this(null);
050    }
051
052    protected AbstractContributionRegistry(AbstractContributionRegistry<K, T> parent) {
053        registry = new HashMap<Object, Contribution<K, T>>();
054        this.parent = parent;
055        listeners = new ArrayList<AbstractContributionRegistry<K, T>>();
056        // subclasses may call importParentContributions(); after initializing the registry
057        // this will import all resolved contributions from the parent
058    }
059
060    public ContributionRegistry<K, T> getParent() {
061        return parent;
062    }
063
064    protected synchronized void importParentContributions() {
065        AbstractContributionRegistry<K, T> pParent = parent;
066        List<AbstractContributionRegistry<K, T>> parents = new ArrayList<AbstractContributionRegistry<K, T>>();
067        while (pParent != null) {
068            parents.add(pParent);
069            pParent = pParent.parent;
070        }
071        Collections.reverse(parents);
072        for (AbstractContributionRegistry<K, T> p : parents) {
073            p.listeners.add(this);
074            for (Contribution<K, T> contrib : p.registry.values().toArray(new Contribution[p.registry.size()])) {
075                if (contrib.isResolved()) {
076                    installContribution(contrib.getId(), contrib.getValue());
077                }
078            }
079            p = p.parent;
080        }
081    }
082
083    public synchronized Contribution<K, T> getContribution(K primaryKey) {
084        Contribution<K, T> contrib = registry.get(primaryKey);
085        if (contrib == null && parent != null) {
086            contrib = parent.getContribution(primaryKey);
087        }
088        return contrib;
089    }
090
091    public T getObject(K key) {
092        Contribution<K, T> contrib = getContribution(key);
093        if (contrib != null) {
094            if (contrib.isResolved()) {
095                return contrib.getValue();
096            }
097        }
098        return null;
099    }
100
101    public synchronized void removeContribution(K key) {
102        Contribution<K, T> contrib = registry.get(key);
103        if (contrib != null) {
104            contrib.unregister();
105        }
106        // TODO if all dependents are unregistered remove contribution from
107        // registry
108    }
109
110    public void removeFragment(K key, T fragment) {
111        Contribution<K, T> contrib = registry.get(key);
112        if (contrib != null) {
113            contrib.removeFragment(fragment);
114        }
115    }
116
117    public synchronized Contribution<K, T> addFragment(K key, T fragment, K... superKeys) {
118        Contribution<K, T> contrib = registry.get(key);
119        if (contrib == null) {
120            contrib = new ContributionImpl<K, T>(this, key);
121            registry.put(key, contrib);
122        }
123        contrib.addFragment(fragment, superKeys);
124        return contrib;
125    }
126
127    public synchronized Contribution<K, T> getOrCreateDependency(K key) {
128        Contribution<K, T> contrib = getContribution(key);
129        if (contrib == null) {
130            contrib = new ContributionImpl<K, T>(this, key);
131            registry.put(key, contrib);
132            // do not register so that this contribution will be a phantom
133        }
134        return contrib;
135    }
136
137    public void fireUnresolved(Contribution<K, T> contrib, T value) {
138        K key = contrib.getId();
139        uninstallContribution(key, value);
140        if (!listeners.isEmpty()) {
141            for (AbstractContributionRegistry<K, T> reg : listeners) {
142                reg.uninstallContribution(key, value);
143            }
144        }
145    }
146
147    public void fireResolved(Contribution<K, T> contrib) {
148        K key = contrib.getId();
149        T value = contrib.getValue();
150        if (value == null) {
151            throw new IllegalStateException("contribution is null");
152        }
153        installContribution(key, value);
154        if (!listeners.isEmpty()) {
155            for (AbstractContributionRegistry<K, T> reg : listeners) {
156                reg.installContribution(key, value);
157            }
158        }
159    }
160
161    public void fireUpdated(T oldValue, Contribution<K, T> contrib) {
162        T value = contrib.getValue();
163        if (value == null) {
164            throw new IllegalStateException("contribution is null");
165        }
166        updateContribution(contrib.getId(), value, oldValue);
167    }
168
169    public void dispose() {
170        registry.clear();
171    }
172
173    protected abstract T clone(T object);
174
175    /**
176     * Applies fragment over the given object.
177     *
178     * @param object
179     * @param fragment
180     */
181    protected void applyFragment(T object, T fragment) {
182        // do nothing
183    }
184
185    protected void applySuperFragment(T object, T superFragment) {
186        // do nothing
187    }
188
189    protected abstract void installContribution(K key, T object);
190
191    protected abstract void uninstallContribution(K key, T object);
192
193    protected boolean isMainFragment(T object) {
194        return true;
195    }
196
197    protected void updateContribution(K key, T object, T oldValue) {
198        uninstallContribution(key, oldValue);
199        installContribution(key, object);
200    }
201
202}