001/*
002 * (C) Copyright 2012-2016 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 *     Anahide Tchertchian
018 *     Florent Guillaume
019 */
020package org.nuxeo.ecm.directory;
021
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033
034/**
035 * Generic {@link BaseDirectoryDescriptor} registry holding registered descriptors and instantiated {@link Directory}
036 * objects.
037 * <p>
038 * The directory descriptors have two special boolean flags that control how merge works:
039 * <ul>
040 * <li>{@code remove="true"}: this removes the definition of the directory. The next definition (if any) will be done
041 * from scratch.
042 * <li>{@code template="true"}: this defines an abstract descriptor which cannot be directly instantiated as a
043 * directory. However another descriptor can extend it through {@code extends="templatename"} to inherit all its
044 * properties.
045 * </ul>
046 *
047 * @since 8.2
048 */
049public class DirectoryRegistry {
050
051    private static final Log log = LogFactory.getLog(DirectoryRegistry.class);
052
053    /** All descriptors registered. */
054    // used under synchronization
055    protected Map<String, List<BaseDirectoryDescriptor>> allDescriptors = new HashMap<>();
056
057    /** Effective descriptors. */
058    // used under synchronization
059    protected Map<String, BaseDirectoryDescriptor> descriptors = new HashMap<>();
060
061    /** Effective instantiated directories. */
062    // used under synchronization
063    protected Map<String, Directory> directories = new HashMap<>();
064
065    public synchronized void addContribution(BaseDirectoryDescriptor contrib) {
066        String id = contrib.name;
067        log.info("Registered directory" + (contrib.template ? " template" : "") + ": " + id);
068        allDescriptors.computeIfAbsent(id, k -> new ArrayList<>()).add(contrib);
069        contributionChanged(contrib);
070    }
071
072    public synchronized void removeContribution(BaseDirectoryDescriptor contrib) {
073        String id = contrib.name;
074        log.info("Unregistered directory" + (contrib.template ? " template" : "") + ": " + id);
075        allDescriptors.getOrDefault(id, Collections.emptyList()).remove(contrib);
076        contributionChanged(contrib);
077    }
078
079    protected void contributionChanged(BaseDirectoryDescriptor contrib) {
080        LinkedList<String> todo = new LinkedList<>();
081        todo.add(contrib.name);
082        Set<String> done = new HashSet<String>();
083        while (!todo.isEmpty()) {
084            String id = todo.removeFirst();
085            if (!done.add(id)) {
086                // already done, avoid loops
087                continue;
088            }
089            BaseDirectoryDescriptor desc = recomputeDescriptor(id);
090            // recompute dependencies
091            if (desc != null) {
092                for (List<BaseDirectoryDescriptor> list : allDescriptors.values()) {
093                    for (BaseDirectoryDescriptor d : list) {
094                        if (id.equals(d.extendz)) {
095                            todo.add(d.name);
096                            break;
097                        }
098                    }
099                }
100            }
101        }
102    }
103
104    protected void removeDirectory(String id) {
105        Directory dir = directories.remove(id);
106        if (dir != null) {
107            shutdownDirectory(dir);
108        }
109    }
110
111    /** Recomputes the effective descriptor for a directory id. */
112    protected BaseDirectoryDescriptor recomputeDescriptor(String id) {
113        removeDirectory(id);
114        // compute effective descriptor
115        List<BaseDirectoryDescriptor> list = allDescriptors.getOrDefault(id, Collections.emptyList());
116        BaseDirectoryDescriptor contrib = null;
117        for (BaseDirectoryDescriptor next : list) {
118            String extendz = next.extendz;
119            if (extendz != null) {
120                // merge from base
121                BaseDirectoryDescriptor base = descriptors.get(extendz);
122                if (base != null && base.template) {
123                    // merge generic base descriptor into specific one from the template
124                    contrib = base.clone();
125                    contrib.template = false;
126                    contrib.name = next.name;
127                    contrib.merge(next);
128                } else {
129                    log.debug("Directory " + id + " extends non-existing directory template: " + extendz);
130                    contrib = null;
131                }
132            } else if (next.remove) {
133                contrib = null;
134            } else if (contrib == null) {
135                // first descriptor or first one after a remove
136                contrib = next.clone();
137            } else if (contrib.getClass() == next.getClass()) {
138                contrib.merge(next);
139            } else {
140                log.warn("Directory " + id + " redefined with different factory");
141                contrib = next.clone();
142            }
143        }
144        if (contrib == null) {
145            descriptors.remove(id);
146        } else {
147            descriptors.put(id, contrib);
148        }
149        return contrib;
150    }
151
152    /**
153     * Gets the effective directory descriptor with the given id.
154     *
155     * @param id the directory id
156     * @return the effective directory descriptor, or {@code null} if not found
157     */
158    public synchronized BaseDirectoryDescriptor getDirectoryDescriptor(String id) {
159        return descriptors.get(id);
160    }
161
162    /**
163     * Gets the directory with the given id.
164     *
165     * @param id the directory id
166     * @return the directory, or {@code null} if not found
167     */
168    public synchronized Directory getDirectory(String id) {
169        Directory dir = directories.get(id);
170        if (dir == null) {
171            BaseDirectoryDescriptor descriptor = descriptors.get(id);
172            if (descriptor != null) {
173                dir = descriptor.newDirectory();
174                directories.put(id,  dir);
175            }
176        }
177        return dir;
178    }
179
180    /**
181     * Gets all the directory ids.
182     *
183     * @return the directory ids
184     */
185    public synchronized List<String> getDirectoryIds() {
186        List<String> list = new ArrayList<>();
187        for (BaseDirectoryDescriptor descriptor : descriptors.values()) {
188            if (descriptor.template) {
189                continue;
190            }
191            list.add(descriptor.name);
192        }
193        return list;
194    }
195
196    /**
197     * Gets all the directories.
198     *
199     * @return the directories
200     */
201    public synchronized List<Directory> getDirectories() {
202        List<Directory> list = new ArrayList<>();
203        for (BaseDirectoryDescriptor descriptor : descriptors.values()) {
204            if (descriptor.template) {
205                continue;
206            }
207            list.add(getDirectory(descriptor.name));
208        }
209        return list;
210    }
211
212    /**
213     * Shuts down all directories and clears the registry.
214     */
215    public synchronized void shutdown() {
216        for (Directory dir : directories.values()) {
217            shutdownDirectory(dir);
218        }
219        allDescriptors.clear();
220        descriptors.clear();
221        directories.clear();
222    }
223
224    /**
225     * Shuts down the given directory and catches any {@link DirectoryException}.
226     *
227     * @param dir the directory
228     */
229    protected static void shutdownDirectory(Directory dir) {
230        try {
231            dir.shutdown();
232        } catch (DirectoryException e) {
233            log.error("Error while shutting down directory:" + dir.getName(), e);
234        }
235    }
236
237}