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