001/* 002 * (C) Copyright 2006-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 * Nuxeo - initial API and implementation 018 * Florent Guillaume 019 */ 020package org.nuxeo.ecm.directory; 021 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.List; 025 026import org.apache.commons.lang.StringUtils; 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029import org.nuxeo.common.xmap.annotation.XNode; 030import org.nuxeo.common.xmap.annotation.XNodeList; 031import org.nuxeo.common.xmap.annotation.XObject; 032import org.nuxeo.ecm.directory.api.DirectoryDeleteConstraint; 033 034/** 035 * Basic directory descriptor, containing the basic fields used by all directories. 036 * 037 * @since 8.2 038 */ 039@XObject(value = "directory") 040public class BaseDirectoryDescriptor implements Cloneable { 041 042 private static final Log log = LogFactory.getLog(BaseDirectoryDescriptor.class); 043 044 /** 045 * How directory semi-"fulltext" searches are matched with a query string. 046 * <p> 047 * Used for SQL and LDAP directories. 048 * 049 * @since 8.2 050 */ 051 public enum SubstringMatchType { 052 /** Matches initial substring. */ 053 subinitial, 054 /** Matches final substring. */ 055 subfinal, 056 /** Matches any substring. */ 057 subany; 058 } 059 060 public static final boolean AUTO_INCREMENT_ID_FIELD_DEFAULT = false; 061 062 public static final int CACHE_TIMEOUT_DEFAULT = 0; 063 064 public static final int CACHE_MAX_SIZE_DEFAULT = 0; 065 066 public static final boolean READ_ONLY_DEFAULT = false; 067 068 public static final SubstringMatchType SUBSTRING_MATCH_TYPE_DEFAULT = SubstringMatchType.subinitial; 069 070 public static final char DEFAULT_DATA_FILE_CHARACTER_SEPARATOR = ','; 071 072 /** 073 * Doesn't create or modify the table in any way. 074 */ 075 public static final String CREATE_TABLE_POLICY_NEVER = "never"; 076 077 /** 078 * Always recreates the table from scratch and loads the CSV data. 079 */ 080 public static final String CREATE_TABLE_POLICY_ALWAYS = "always"; 081 082 /** 083 * If the table doesn't exist then creates it and loads the CSV data. If the table already exists, only adds missing 084 * columns (with null values). 085 */ 086 public static final String CREATE_TABLE_POLICY_ON_MISSING_COLUMNS = "on_missing_columns"; 087 088 public static final String CREATE_TABLE_POLICY_DEFAULT = CREATE_TABLE_POLICY_NEVER; 089 090 public static final List<String> CREATE_TABLE_POLICIES = Arrays.asList(CREATE_TABLE_POLICY_NEVER, 091 CREATE_TABLE_POLICY_ALWAYS, CREATE_TABLE_POLICY_ON_MISSING_COLUMNS); 092 093 @XNode("@name") 094 public String name; 095 096 @XNode("@remove") 097 public boolean remove; 098 099 @XNode("@template") 100 public boolean template; 101 102 @XNode("@extends") 103 public String extendz; 104 105 @XNode("parentDirectory") 106 public String parentDirectory; 107 108 @XNode("schema") 109 public String schemaName; 110 111 @XNode("idField") 112 public String idField; 113 114 @XNode("autoincrementIdField") 115 public Boolean autoincrementIdField; 116 117 public boolean isAutoincrementIdField() { 118 return autoincrementIdField == null ? AUTO_INCREMENT_ID_FIELD_DEFAULT : autoincrementIdField.booleanValue(); 119 } 120 121 public void setAutoincrementIdField(boolean autoincrementIdField) { 122 this.autoincrementIdField = Boolean.valueOf(autoincrementIdField); 123 } 124 125 @XNode("table") 126 public String tableName; 127 128 @XNode("readOnly") 129 public Boolean readOnly; 130 131 @XNode("passwordField") 132 public String passwordField; 133 134 @XNode("passwordHashAlgorithm") 135 public String passwordHashAlgorithm; 136 137 @XNodeList(value = "permissions/permission", type = PermissionDescriptor[].class, componentType = PermissionDescriptor.class) 138 public PermissionDescriptor[] permissions; 139 140 @XNode("cacheTimeout") 141 public Integer cacheTimeout; 142 143 @XNode("cacheMaxSize") 144 public Integer cacheMaxSize; 145 146 @XNode("cacheEntryName") 147 public String cacheEntryName; 148 149 @XNode("cacheEntryWithoutReferencesName") 150 public String cacheEntryWithoutReferencesName; 151 152 @XNode("negativeCaching") 153 public Boolean negativeCaching; 154 155 @XNode("substringMatchType") 156 public String substringMatchType; 157 158 /** 159 * @since 8.4 160 */ 161 @XNodeList(value = "types/type", type = String[].class, componentType = String.class) 162 public String[] types; 163 164 /** 165 * @since 8.4 166 */ 167 @XNodeList(value = "deleteConstraint", type = ArrayList.class, componentType = DirectoryDeleteConstraintDescriptor.class) 168 List<DirectoryDeleteConstraintDescriptor> deleteConstraints; 169 170 @XNode("dataFile") 171 public String dataFileName; 172 173 public String getDataFileName() { 174 return dataFileName; 175 } 176 177 @XNode(value = "dataFileCharacterSeparator", trim = false) 178 public String dataFileCharacterSeparator = ","; 179 180 public char getDataFileCharacterSeparator() { 181 char sep; 182 if (StringUtils.isEmpty(dataFileCharacterSeparator)) { 183 sep = DEFAULT_DATA_FILE_CHARACTER_SEPARATOR; 184 } else { 185 sep = dataFileCharacterSeparator.charAt(0); 186 if (dataFileCharacterSeparator.length() > 1) { 187 log.warn("More than one character found for character separator, will use the first one \"" + sep 188 + "\""); 189 } 190 } 191 return sep; 192 } 193 194 @XNode("createTablePolicy") 195 public String createTablePolicy; 196 197 public String getCreateTablePolicy() { 198 if (StringUtils.isBlank(createTablePolicy)) { 199 return CREATE_TABLE_POLICY_DEFAULT; 200 } 201 String ctp = createTablePolicy.toLowerCase(); 202 if (!CREATE_TABLE_POLICIES.contains(ctp)) { 203 throw new DirectoryException("Invalid createTablePolicy: " + createTablePolicy + ", it should be one of: " 204 + CREATE_TABLE_POLICIES); 205 } 206 return ctp; 207 } 208 209 public boolean isReadOnly() { 210 return readOnly == null ? READ_ONLY_DEFAULT : readOnly.booleanValue(); 211 } 212 213 public void setReadOnly(boolean readOnly) { 214 this.readOnly = Boolean.valueOf(readOnly); 215 } 216 217 public int getCacheTimeout() { 218 return cacheTimeout == null ? CACHE_TIMEOUT_DEFAULT : cacheTimeout.intValue(); 219 } 220 221 public int getCacheMaxSize() { 222 return cacheMaxSize == null ? CACHE_MAX_SIZE_DEFAULT : cacheMaxSize.intValue(); 223 } 224 225 public SubstringMatchType getSubstringMatchType() { 226 if (StringUtils.isBlank(substringMatchType)) { 227 return SUBSTRING_MATCH_TYPE_DEFAULT; 228 } 229 try { 230 return SubstringMatchType.valueOf(substringMatchType); 231 } catch (IllegalArgumentException e) { 232 log.error("Unknown value for <substringMatchType>: " + substringMatchType); 233 return SUBSTRING_MATCH_TYPE_DEFAULT; 234 } 235 } 236 237 /** 238 * Sub-classes MUST OVERRIDE and use a more specific return type. 239 * <p> 240 * Usually it's bad to use clone(), and a copy-constructor is preferred, but here we want the copy method to be 241 * inheritable so clone() is appropriate. 242 * <p> 243 * {@inheritDoc} 244 */ 245 @Override 246 public BaseDirectoryDescriptor clone() { 247 BaseDirectoryDescriptor clone; 248 try { 249 clone = (BaseDirectoryDescriptor) super.clone(); 250 } catch (CloneNotSupportedException e) { 251 throw new AssertionError(e); 252 } 253 // basic fields are already copied by super.clone() 254 if (permissions != null) { 255 clone.permissions = new PermissionDescriptor[permissions.length]; 256 for (int i = 0; i < permissions.length; i++) { 257 clone.permissions[i] = permissions[i].clone(); 258 } 259 } 260 return clone; 261 } 262 263 public void merge(BaseDirectoryDescriptor other) { 264 template = template || other.template; 265 266 if (other.parentDirectory != null) { 267 parentDirectory = other.parentDirectory; 268 } 269 if (other.schemaName != null) { 270 schemaName = other.schemaName; 271 } 272 if (other.idField != null) { 273 idField = other.idField; 274 } 275 if (other.autoincrementIdField != null) { 276 autoincrementIdField = other.autoincrementIdField; 277 } 278 if (other.tableName != null) { 279 tableName = other.tableName; 280 } 281 if (other.readOnly != null) { 282 readOnly = other.readOnly; 283 } 284 if (other.passwordField != null) { 285 passwordField = other.passwordField; 286 } 287 if (other.passwordHashAlgorithm != null) { 288 passwordHashAlgorithm = other.passwordHashAlgorithm; 289 } 290 if (other.permissions != null && other.permissions.length != 0) { 291 permissions = other.permissions; 292 } 293 if (other.cacheTimeout != null) { 294 cacheTimeout = other.cacheTimeout; 295 } 296 if (other.cacheMaxSize != null) { 297 cacheMaxSize = other.cacheMaxSize; 298 } 299 if (other.cacheEntryName != null) { 300 cacheEntryName = other.cacheEntryName; 301 } 302 if (other.cacheEntryWithoutReferencesName != null) { 303 cacheEntryWithoutReferencesName = other.cacheEntryWithoutReferencesName; 304 } 305 if (other.negativeCaching != null) { 306 negativeCaching = other.negativeCaching; 307 } 308 if (other.substringMatchType != null) { 309 substringMatchType = other.substringMatchType; 310 } 311 if (other.types != null) { 312 types = other.types; 313 } 314 if (other.deleteConstraints != null) { 315 deleteConstraints = other.deleteConstraints; 316 } 317 if (other.dataFileName != null) { 318 dataFileName = other.dataFileName; 319 } 320 if (other.dataFileCharacterSeparator != null) { 321 dataFileCharacterSeparator = other.dataFileCharacterSeparator; 322 } 323 if (other.createTablePolicy != null) { 324 createTablePolicy = other.createTablePolicy; 325 } 326 } 327 328 /** 329 * Creates a new {@link Directory} instance from this {@link DirectoryDescriptor). 330 */ 331 public Directory newDirectory() { 332 throw new UnsupportedOperationException("Cannot be instantiated as Directory: " + getClass().getName()); 333 } 334 335 /** 336 * @since 8.4 337 */ 338 public List<DirectoryDeleteConstraint> getDeleteConstraints() throws DirectoryException { 339 List<DirectoryDeleteConstraint> res = new ArrayList<DirectoryDeleteConstraint>(); 340 if (deleteConstraints != null) { 341 for (DirectoryDeleteConstraintDescriptor deleteConstraintDescriptor : deleteConstraints) { 342 res.add(deleteConstraintDescriptor.getDeleteConstraint()); 343 } 344 } 345 return res; 346 } 347 348}