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}