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