001/* 002 * (C) Copyright 2006-2018 Nuxeo (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.lang3.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 @XNode("computeMultiTenantId") 159 protected boolean computeMultiTenantId = true; 160 161 /** 162 * @since 8.4 163 */ 164 @XNodeList(value = "types/type", type = String[].class, componentType = String.class) 165 public String[] types; 166 167 /** 168 * @since 8.4 169 */ 170 @XNodeList(value = "deleteConstraint", type = ArrayList.class, componentType = DirectoryDeleteConstraintDescriptor.class) 171 List<DirectoryDeleteConstraintDescriptor> deleteConstraints; 172 173 /** 174 * @since 9.2 175 */ 176 @XNodeList(value = "references/reference", type = ReferenceDescriptor[].class, componentType = ReferenceDescriptor.class) 177 ReferenceDescriptor[] references; 178 179 /** 180 * @since 9.2 181 */ 182 @XNodeList(value = "references/inverseReference", type = InverseReferenceDescriptor[].class, componentType = InverseReferenceDescriptor.class) 183 InverseReferenceDescriptor[] inverseReferences; 184 185 @XNode("dataFile") 186 public String dataFileName; 187 188 public String getDataFileName() { 189 return dataFileName; 190 } 191 192 @XNode(value = "dataFileCharacterSeparator", trim = false) 193 public String dataFileCharacterSeparator = ","; 194 195 public char getDataFileCharacterSeparator() { 196 char sep; 197 if (StringUtils.isEmpty(dataFileCharacterSeparator)) { 198 sep = DEFAULT_DATA_FILE_CHARACTER_SEPARATOR; 199 } else { 200 sep = dataFileCharacterSeparator.charAt(0); 201 if (dataFileCharacterSeparator.length() > 1) { 202 log.warn("More than one character found for character separator, will use the first one \"" + sep 203 + "\""); 204 } 205 } 206 return sep; 207 } 208 209 @XNode("createTablePolicy") 210 public String createTablePolicy; 211 212 public String getCreateTablePolicy() { 213 if (StringUtils.isBlank(createTablePolicy)) { 214 return CREATE_TABLE_POLICY_DEFAULT; 215 } 216 String ctp = createTablePolicy.toLowerCase(); 217 if (!CREATE_TABLE_POLICIES.contains(ctp)) { 218 throw new DirectoryException("Invalid createTablePolicy: " + createTablePolicy + ", it should be one of: " 219 + CREATE_TABLE_POLICIES); 220 } 221 return ctp; 222 } 223 224 public boolean isReadOnly() { 225 return readOnly == null ? READ_ONLY_DEFAULT : readOnly.booleanValue(); 226 } 227 228 public void setReadOnly(boolean readOnly) { 229 this.readOnly = Boolean.valueOf(readOnly); 230 } 231 232 public int getCacheTimeout() { 233 return cacheTimeout == null ? CACHE_TIMEOUT_DEFAULT : cacheTimeout.intValue(); 234 } 235 236 public int getCacheMaxSize() { 237 return cacheMaxSize == null ? CACHE_MAX_SIZE_DEFAULT : cacheMaxSize.intValue(); 238 } 239 240 public SubstringMatchType getSubstringMatchType() { 241 if (StringUtils.isBlank(substringMatchType)) { 242 return SUBSTRING_MATCH_TYPE_DEFAULT; 243 } 244 try { 245 return SubstringMatchType.valueOf(substringMatchType); 246 } catch (IllegalArgumentException e) { 247 log.error("Unknown value for <substringMatchType>: " + substringMatchType); 248 return SUBSTRING_MATCH_TYPE_DEFAULT; 249 } 250 } 251 252 /** 253 * Sub-classes MUST OVERRIDE and use a more specific return type. 254 * <p> 255 * Usually it's bad to use clone(), and a copy-constructor is preferred, but here we want the copy method to be 256 * inheritable so clone() is appropriate. 257 * <p> 258 * {@inheritDoc} 259 */ 260 @Override 261 public BaseDirectoryDescriptor clone() { 262 BaseDirectoryDescriptor clone; 263 try { 264 clone = (BaseDirectoryDescriptor) super.clone(); 265 } catch (CloneNotSupportedException e) { 266 throw new AssertionError(e); 267 } 268 // basic fields are already copied by super.clone() 269 if (permissions != null) { 270 clone.permissions = new PermissionDescriptor[permissions.length]; 271 for (int i = 0; i < permissions.length; i++) { 272 clone.permissions[i] = permissions[i].clone(); 273 } 274 } 275 if (references != null) { 276 clone.references = Arrays.stream(references) 277 .map(ReferenceDescriptor::clone) 278 .toArray(ReferenceDescriptor[]::new); 279 } 280 if (inverseReferences != null) { 281 clone.inverseReferences = Arrays.stream(inverseReferences).map(InverseReferenceDescriptor::clone).toArray( 282 InverseReferenceDescriptor[]::new); 283 } 284 return clone; 285 } 286 287 public void merge(BaseDirectoryDescriptor other) { 288 template = template || other.template; 289 290 if (other.parentDirectory != null) { 291 parentDirectory = other.parentDirectory; 292 } 293 if (other.schemaName != null) { 294 schemaName = other.schemaName; 295 } 296 if (other.idField != null) { 297 idField = other.idField; 298 } 299 if (other.autoincrementIdField != null) { 300 autoincrementIdField = other.autoincrementIdField; 301 } 302 if (other.tableName != null) { 303 tableName = other.tableName; 304 } 305 if (other.readOnly != null) { 306 readOnly = other.readOnly; 307 } 308 if (other.passwordField != null) { 309 passwordField = other.passwordField; 310 } 311 if (other.passwordHashAlgorithm != null) { 312 passwordHashAlgorithm = other.passwordHashAlgorithm; 313 } 314 if (other.permissions != null && other.permissions.length != 0) { 315 permissions = other.permissions; 316 } 317 if (other.cacheTimeout != null) { 318 cacheTimeout = other.cacheTimeout; 319 } 320 if (other.cacheMaxSize != null) { 321 cacheMaxSize = other.cacheMaxSize; 322 } 323 if (other.cacheEntryName != null) { 324 cacheEntryName = other.cacheEntryName; 325 } 326 if (other.cacheEntryWithoutReferencesName != null) { 327 cacheEntryWithoutReferencesName = other.cacheEntryWithoutReferencesName; 328 } 329 if (other.negativeCaching != null) { 330 negativeCaching = other.negativeCaching; 331 } 332 if (other.substringMatchType != null) { 333 substringMatchType = other.substringMatchType; 334 } 335 if (other.types != null) { 336 types = other.types; 337 } 338 if (other.deleteConstraints != null) { 339 deleteConstraints = other.deleteConstraints; 340 } 341 if (other.dataFileName != null) { 342 dataFileName = other.dataFileName; 343 } 344 if (other.dataFileCharacterSeparator != null) { 345 dataFileCharacterSeparator = other.dataFileCharacterSeparator; 346 } 347 if (other.createTablePolicy != null) { 348 createTablePolicy = other.createTablePolicy; 349 } 350 if (other.references != null && other.references.length != 0) { 351 references = other.references; 352 } 353 if (other.inverseReferences != null && other.inverseReferences.length != 0) { 354 inverseReferences = other.inverseReferences; 355 } 356 computeMultiTenantId = other.computeMultiTenantId; 357 } 358 359 /** 360 * Creates a new {@link Directory} instance from this {@link BaseDirectoryDescriptor). 361 */ 362 public Directory newDirectory() { 363 throw new UnsupportedOperationException("Cannot be instantiated as Directory: " + getClass().getName()); 364 } 365 366 /** 367 * @since 8.4 368 */ 369 public List<DirectoryDeleteConstraint> getDeleteConstraints() { 370 List<DirectoryDeleteConstraint> res = new ArrayList<>(); 371 if (deleteConstraints != null) { 372 for (DirectoryDeleteConstraintDescriptor deleteConstraintDescriptor : deleteConstraints) { 373 res.add(deleteConstraintDescriptor.getDeleteConstraint()); 374 } 375 } 376 return res; 377 } 378 379 /** 380 * @since 9.2 381 */ 382 public ReferenceDescriptor[] getReferences() { 383 return references; 384 } 385 386 /** 387 * @since 9.2 388 */ 389 public InverseReferenceDescriptor[] getInverseReferences() { 390 return inverseReferences; 391 } 392 393 /** 394 * Returns {@code true} if a multi tenant id should be computed for this directory, if the directory has support for 395 * multi tenancy, {@code false} otherwise. 396 * 397 * @since 10.1 398 */ 399 public boolean isComputeMultiTenantId() { 400 return computeMultiTenantId; 401 } 402 403}