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 if (id.contains("/") && log.isWarnEnabled()) { 068 log.warn("Directory " + id + " should not contain forward slashes in its name, as they are not supported." 069 + " Operations with the REST API on this directory won't work."); 070 } 071 log.info("Registered directory" + (contrib.template ? " template" : "") + ": " + id); 072 allDescriptors.computeIfAbsent(id, k -> new ArrayList<>()).add(contrib); 073 contributionChanged(contrib); 074 } 075 076 public synchronized void removeContribution(BaseDirectoryDescriptor contrib) { 077 String id = contrib.name; 078 log.info("Unregistered directory" + (contrib.template ? " template" : "") + ": " + id); 079 allDescriptors.getOrDefault(id, Collections.emptyList()).remove(contrib); 080 contributionChanged(contrib); 081 } 082 083 protected void contributionChanged(BaseDirectoryDescriptor contrib) { 084 LinkedList<String> todo = new LinkedList<>(); 085 todo.add(contrib.name); 086 Set<String> done = new HashSet<>(); 087 while (!todo.isEmpty()) { 088 String id = todo.removeFirst(); 089 if (!done.add(id)) { 090 // already done, avoid loops 091 continue; 092 } 093 BaseDirectoryDescriptor desc = recomputeDescriptor(id); 094 // recompute dependencies 095 if (desc != null) { 096 for (List<BaseDirectoryDescriptor> list : allDescriptors.values()) { 097 for (BaseDirectoryDescriptor d : list) { 098 if (id.equals(d.extendz)) { 099 todo.add(d.name); 100 break; 101 } 102 } 103 } 104 } 105 } 106 } 107 108 protected void removeDirectory(String id) { 109 Directory dir = directories.remove(id); 110 if (dir != null) { 111 shutdownDirectory(dir); 112 } 113 } 114 115 /** Recomputes the effective descriptor for a directory id. */ 116 protected BaseDirectoryDescriptor recomputeDescriptor(String id) { 117 removeDirectory(id); 118 // compute effective descriptor 119 List<BaseDirectoryDescriptor> list = allDescriptors.getOrDefault(id, Collections.emptyList()); 120 BaseDirectoryDescriptor contrib = null; 121 for (BaseDirectoryDescriptor next : list) { 122 String extendz = next.extendz; 123 if (extendz != null) { 124 // merge from base 125 BaseDirectoryDescriptor base = descriptors.get(extendz); 126 if (base != null && base.template) { 127 // merge generic base descriptor into specific one from the template 128 contrib = base.clone(); 129 contrib.template = false; 130 contrib.name = next.name; 131 contrib.merge(next); 132 } else { 133 log.debug("Directory " + id + " extends non-existing directory template: " + extendz); 134 contrib = null; 135 } 136 } else if (next.remove) { 137 contrib = null; 138 } else if (contrib == null) { 139 // first descriptor or first one after a remove 140 contrib = next.clone(); 141 } else if (contrib.getClass() == next.getClass()) { 142 contrib.merge(next); 143 } else { 144 log.warn("Directory " + id + " redefined with different factory"); 145 contrib = next.clone(); 146 } 147 } 148 if (contrib == null) { 149 descriptors.remove(id); 150 } else { 151 descriptors.put(id, contrib); 152 } 153 return contrib; 154 } 155 156 /** 157 * Gets the effective directory descriptor with the given id. 158 * <p> 159 * Templates are not returned. 160 * 161 * @param id the directory id 162 * @return the effective directory descriptor, or {@code null} if not found 163 */ 164 public synchronized BaseDirectoryDescriptor getDirectoryDescriptor(String id) { 165 BaseDirectoryDescriptor descriptor = descriptors.get(id); 166 return descriptor.template ? null : descriptor; 167 } 168 169 /** 170 * Gets the directory with the given id. 171 * 172 * @param id the directory id 173 * @return the directory, or {@code null} if not found 174 */ 175 public synchronized Directory getDirectory(String id) { 176 Directory dir = directories.get(id); 177 if (dir == null) { 178 BaseDirectoryDescriptor descriptor = descriptors.get(id); 179 if (descriptor != null && !descriptor.template) { 180 dir = descriptor.newDirectory(); 181 directories.put(id, dir); 182 } 183 } 184 return dir; 185 } 186 187 /** 188 * Gets all the directory ids. 189 * 190 * @return the directory ids 191 */ 192 public synchronized List<String> getDirectoryIds() { 193 List<String> list = new ArrayList<>(); 194 for (BaseDirectoryDescriptor descriptor : descriptors.values()) { 195 if (descriptor.template) { 196 continue; 197 } 198 list.add(descriptor.name); 199 } 200 return list; 201 } 202 203 /** 204 * Gets all the directories. 205 * 206 * @return the directories 207 */ 208 public synchronized List<Directory> getDirectories() { 209 List<Directory> list = new ArrayList<>(); 210 for (BaseDirectoryDescriptor descriptor : descriptors.values()) { 211 if (descriptor.template) { 212 continue; 213 } 214 list.add(getDirectory(descriptor.name)); 215 } 216 return list; 217 } 218 219 /** 220 * Shuts down all directories and clears the registry. 221 */ 222 public synchronized void shutdown() { 223 for (Directory dir : directories.values()) { 224 shutdownDirectory(dir); 225 } 226 allDescriptors.clear(); 227 descriptors.clear(); 228 directories.clear(); 229 } 230 231 /** 232 * Shuts down the given directory and catches any {@link DirectoryException}. 233 * 234 * @param dir the directory 235 */ 236 protected static void shutdownDirectory(Directory dir) { 237 try { 238 dir.shutdown(); 239 } catch (DirectoryException e) { 240 log.error("Error while shutting down directory:" + dir.getName(), e); 241 } 242 } 243 244}