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 /** 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) 279 .map(InverseReferenceDescriptor::clone) 280 .toArray(InverseReferenceDescriptor[]::new); 281 } 282 return clone; 283 } 284 285 public void merge(BaseDirectoryDescriptor other) { 286 template = template || other.template; 287 288 if (other.parentDirectory != null) { 289 parentDirectory = other.parentDirectory; 290 } 291 if (other.schemaName != null) { 292 schemaName = other.schemaName; 293 } 294 if (other.idField != null) { 295 idField = other.idField; 296 } 297 if (other.autoincrementIdField != null) { 298 autoincrementIdField = other.autoincrementIdField; 299 } 300 if (other.tableName != null) { 301 tableName = other.tableName; 302 } 303 if (other.readOnly != null) { 304 readOnly = other.readOnly; 305 } 306 if (other.passwordField != null) { 307 passwordField = other.passwordField; 308 } 309 if (other.passwordHashAlgorithm != null) { 310 passwordHashAlgorithm = other.passwordHashAlgorithm; 311 } 312 if (other.permissions != null && other.permissions.length != 0) { 313 permissions = other.permissions; 314 } 315 if (other.cacheTimeout != null) { 316 cacheTimeout = other.cacheTimeout; 317 } 318 if (other.cacheMaxSize != null) { 319 cacheMaxSize = other.cacheMaxSize; 320 } 321 if (other.cacheEntryName != null) { 322 cacheEntryName = other.cacheEntryName; 323 } 324 if (other.cacheEntryWithoutReferencesName != null) { 325 cacheEntryWithoutReferencesName = other.cacheEntryWithoutReferencesName; 326 } 327 if (other.negativeCaching != null) { 328 negativeCaching = other.negativeCaching; 329 } 330 if (other.substringMatchType != null) { 331 substringMatchType = other.substringMatchType; 332 } 333 if (other.types != null) { 334 types = other.types; 335 } 336 if (other.deleteConstraints != null) { 337 deleteConstraints = other.deleteConstraints; 338 } 339 if (other.dataFileName != null) { 340 dataFileName = other.dataFileName; 341 } 342 if (other.dataFileCharacterSeparator != null) { 343 dataFileCharacterSeparator = other.dataFileCharacterSeparator; 344 } 345 if (other.createTablePolicy != null) { 346 createTablePolicy = other.createTablePolicy; 347 } 348 if (other.references != null && other.references.length != 0) { 349 references = other.references; 350 } 351 if (other.inverseReferences != null && other.inverseReferences.length != 0) { 352 inverseReferences = other.inverseReferences; 353 } 354 } 355 356 /** 357 * Creates a new {@link Directory} instance from this {@link BaseDirectoryDescriptor). 358 */ 359 public Directory newDirectory() { 360 throw new UnsupportedOperationException("Cannot be instantiated as Directory: " + getClass().getName()); 361 } 362 363 /** 364 * @since 8.4 365 */ 366 public List<DirectoryDeleteConstraint> getDeleteConstraints() throws DirectoryException { 367 List<DirectoryDeleteConstraint> res = new ArrayList<>(); 368 if (deleteConstraints != null) { 369 for (DirectoryDeleteConstraintDescriptor deleteConstraintDescriptor : deleteConstraints) { 370 res.add(deleteConstraintDescriptor.getDeleteConstraint()); 371 } 372 } 373 return res; 374 } 375 376 /** 377 * @since 9.2 378 */ 379 public ReferenceDescriptor[] getReferences() { 380 return references; 381 } 382 383 /** 384 * @since 9.2 385 */ 386 public InverseReferenceDescriptor[] getInverseReferences() { 387 return inverseReferences; 388 } 389 390}