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<String>(); 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 * 159 * @param id the directory id 160 * @return the effective directory descriptor, or {@code null} if not found 161 */ 162 public synchronized BaseDirectoryDescriptor getDirectoryDescriptor(String id) { 163 return descriptors.get(id); 164 } 165 166 /** 167 * Gets the directory with the given id. 168 * 169 * @param id the directory id 170 * @return the directory, or {@code null} if not found 171 */ 172 public synchronized Directory getDirectory(String id) { 173 Directory dir = directories.get(id); 174 if (dir == null) { 175 BaseDirectoryDescriptor descriptor = descriptors.get(id); 176 if (descriptor != null) { 177 dir = descriptor.newDirectory(); 178 directories.put(id, dir); 179 } 180 } 181 return dir; 182 } 183 184 /** 185 * Gets all the directory ids. 186 * 187 * @return the directory ids 188 */ 189 public synchronized List<String> getDirectoryIds() { 190 List<String> list = new ArrayList<>(); 191 for (BaseDirectoryDescriptor descriptor : descriptors.values()) { 192 if (descriptor.template) { 193 continue; 194 } 195 list.add(descriptor.name); 196 } 197 return list; 198 } 199 200 /** 201 * Gets all the directories. 202 * 203 * @return the directories 204 */ 205 public synchronized List<Directory> getDirectories() { 206 List<Directory> list = new ArrayList<>(); 207 for (BaseDirectoryDescriptor descriptor : descriptors.values()) { 208 if (descriptor.template) { 209 continue; 210 } 211 list.add(getDirectory(descriptor.name)); 212 } 213 return list; 214 } 215 216 /** 217 * Shuts down all directories and clears the registry. 218 */ 219 public synchronized void shutdown() { 220 for (Directory dir : directories.values()) { 221 shutdownDirectory(dir); 222 } 223 allDescriptors.clear(); 224 descriptors.clear(); 225 directories.clear(); 226 } 227 228 /** 229 * Shuts down the given directory and catches any {@link DirectoryException}. 230 * 231 * @param dir the directory 232 */ 233 protected static void shutdownDirectory(Directory dir) { 234 try { 235 dir.shutdown(); 236 } catch (DirectoryException e) { 237 log.error("Error while shutting down directory:" + dir.getName(), e); 238 } 239 } 240 241}