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}