1 /* 2 * Copyright (c) 1999, 2003, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.jndi.ldap; 27 28 import javax.naming.*; 29 import javax.naming.directory.*; 30 import java.util.Hashtable; 31 import com.sun.jndi.toolkit.dir.HierMemDirCtx; 32 33 /** 34 * This is the class used to implement LDAP's GetSchema call. 35 * 36 * It subclasses HierMemDirContext for most of the functionality. It 37 * overrides functions that cause the schema definitions to change. 38 * In such a case, it write the schema to the LdapServer and (assuming 39 * there are no errors), calls it's superclass's equivalent function. 40 * Thus, the schema tree and the LDAP server's schema attributes are 41 * always in sync. 42 */ 43 44 final class LdapSchemaCtx extends HierMemDirCtx { 45 46 static private final boolean debug = false; 47 48 private static final int LEAF = 0; // schema object (e.g. attribute type defn) 49 private static final int SCHEMA_ROOT = 1; // schema tree root 50 static final int OBJECTCLASS_ROOT = 2; // root of object class subtree 51 static final int ATTRIBUTE_ROOT = 3; // root of attribute type subtree 52 static final int SYNTAX_ROOT = 4; // root of syntax subtree 53 static final int MATCHRULE_ROOT = 5; // root of matching rule subtree 54 static final int OBJECTCLASS = 6; // an object class definition 55 static final int ATTRIBUTE = 7; // an attribute type definition 56 static final int SYNTAX = 8; // a syntax definition 57 static final int MATCHRULE = 9; // a matching rule definition 58 59 private SchemaInfo info= null; 60 private boolean setupMode = true; 61 62 private int objectType; 63 64 static DirContext createSchemaTree(Hashtable env, String subschemasubentry, 65 LdapCtx schemaEntry, Attributes schemaAttrs, boolean netscapeBug) 66 throws NamingException { 67 try { 68 LdapSchemaParser parser = new LdapSchemaParser(netscapeBug); 69 70 SchemaInfo allinfo = new SchemaInfo(subschemasubentry, 71 schemaEntry, parser); 72 73 LdapSchemaCtx root = new LdapSchemaCtx(SCHEMA_ROOT, env, allinfo); 74 parser.LDAP2JNDISchema(schemaAttrs, root); 75 return root; 76 } catch (NamingException e) { 77 schemaEntry.close(); // cleanup 78 throw e; 79 } 80 } 81 82 // Called by createNewCtx 83 private LdapSchemaCtx(int objectType, Hashtable environment, SchemaInfo info) { 84 super(environment, LdapClient.caseIgnore); 85 86 this.objectType = objectType; 87 this.info = info; 88 } 89 90 // override HierMemDirCtx.close to prevent premature GC of shared data 91 public void close() throws NamingException { 92 info.close(); 93 } 94 95 // override to ignore obj and use attrs 96 // treat same as createSubcontext 97 final public void bind(Name name, Object obj, Attributes attrs) 98 throws NamingException { 99 if (!setupMode) { 100 if (obj != null) { 101 throw new IllegalArgumentException("obj must be null"); 102 } 103 104 // Update server 105 addServerSchema(attrs); 106 } 107 108 // Update in-memory copy 109 LdapSchemaCtx newEntry = 110 (LdapSchemaCtx)super.doCreateSubcontext(name, attrs); 111 } 112 113 final protected void doBind(Name name, Object obj, Attributes attrs, 114 boolean useFactory) throws NamingException { 115 if (!setupMode) { 116 throw new SchemaViolationException( 117 "Cannot bind arbitrary object; use createSubcontext()"); 118 } else { 119 super.doBind(name, obj, attrs, false); // always ignore factories 120 } 121 } 122 123 // override to use bind() instead 124 final public void rebind(Name name, Object obj, Attributes attrs) 125 throws NamingException { 126 try { 127 doLookup(name, false); 128 throw new SchemaViolationException( 129 "Cannot replace existing schema object"); 130 } catch (NameNotFoundException e) { 131 bind(name, obj, attrs); 132 } 133 } 134 135 final protected void doRebind(Name name, Object obj, Attributes attrs, 136 boolean useFactory) throws NamingException { 137 if (!setupMode) { 138 throw new SchemaViolationException( 139 "Cannot bind arbitrary object; use createSubcontext()"); 140 } else { 141 super.doRebind(name, obj, attrs, false); // always ignore factories 142 } 143 } 144 145 final protected void doUnbind(Name name) throws NamingException { 146 if (!setupMode) { 147 // Update server 148 try { 149 // Lookup entry from memory 150 LdapSchemaCtx target = (LdapSchemaCtx)doLookup(name, false); 151 152 deleteServerSchema(target.attrs); 153 } catch (NameNotFoundException e) { 154 return; 155 } 156 } 157 // Update in-memory copy 158 super.doUnbind(name); 159 } 160 161 final protected void doRename(Name oldname, Name newname) 162 throws NamingException { 163 if (!setupMode) { 164 throw new SchemaViolationException("Cannot rename a schema object"); 165 } else { 166 super.doRename(oldname, newname); 167 } 168 } 169 170 final protected void doDestroySubcontext(Name name) throws NamingException { 171 if (!setupMode) { 172 // Update server 173 try { 174 // Lookup entry from memory 175 LdapSchemaCtx target = (LdapSchemaCtx)doLookup(name, false); 176 177 deleteServerSchema(target.attrs); 178 } catch (NameNotFoundException e) { 179 return; 180 } 181 } 182 183 // Update in-memory copy 184 super.doDestroySubcontext(name); 185 } 186 187 // Called to create oc, attr, syntax or matching rule roots and leaf entries 188 final LdapSchemaCtx setup(int objectType, String name, Attributes attrs) 189 throws NamingException{ 190 try { 191 setupMode = true; 192 LdapSchemaCtx answer = 193 (LdapSchemaCtx) super.doCreateSubcontext( 194 new CompositeName(name), attrs); 195 196 answer.objectType = objectType; 197 answer.setupMode = false; 198 return answer; 199 } finally { 200 setupMode = false; 201 } 202 } 203 204 final protected DirContext doCreateSubcontext(Name name, Attributes attrs) 205 throws NamingException { 206 207 if (attrs == null || attrs.size() == 0) { 208 throw new SchemaViolationException( 209 "Must supply attributes describing schema"); 210 } 211 212 if (!setupMode) { 213 // Update server 214 addServerSchema(attrs); 215 } 216 217 // Update in-memory copy 218 LdapSchemaCtx newEntry = 219 (LdapSchemaCtx) super.doCreateSubcontext(name, attrs); 220 return newEntry; 221 } 222 223 final private static Attributes deepClone(Attributes orig) 224 throws NamingException { 225 BasicAttributes copy = new BasicAttributes(true); 226 NamingEnumeration attrs = orig.getAll(); 227 while (attrs.hasMore()) { 228 copy.put((Attribute)((Attribute)attrs.next()).clone()); 229 } 230 return copy; 231 } 232 233 final protected void doModifyAttributes(ModificationItem[] mods) 234 throws NamingException { 235 if (setupMode) { 236 super.doModifyAttributes(mods); 237 } else { 238 Attributes copy = deepClone(attrs); 239 240 // Apply modifications to copy 241 applyMods(mods, copy); 242 243 // Update server copy 244 modifyServerSchema(attrs, copy); 245 246 // Update in-memory copy 247 attrs = copy; 248 } 249 } 250 251 // we override this so the superclass creates the right kind of contexts 252 // Default is to create LEAF objects; caller will change after creation 253 // if necessary 254 final protected HierMemDirCtx createNewCtx() { 255 LdapSchemaCtx ctx = new LdapSchemaCtx(LEAF, myEnv, info); 256 return ctx; 257 } 258 259 260 final private void addServerSchema(Attributes attrs) 261 throws NamingException { 262 Attribute schemaAttr; 263 264 switch (objectType) { 265 case OBJECTCLASS_ROOT: 266 schemaAttr = info.parser.stringifyObjDesc(attrs); 267 break; 268 269 case ATTRIBUTE_ROOT: 270 schemaAttr = info.parser.stringifyAttrDesc(attrs); 271 break; 272 273 case SYNTAX_ROOT: 274 schemaAttr = info.parser.stringifySyntaxDesc(attrs); 275 break; 276 277 case MATCHRULE_ROOT: 278 schemaAttr = info.parser.stringifyMatchRuleDesc(attrs); 279 break; 280 281 case SCHEMA_ROOT: 282 throw new SchemaViolationException( 283 "Cannot create new entry under schema root"); 284 285 default: 286 throw new SchemaViolationException( 287 "Cannot create child of schema object"); 288 } 289 290 Attributes holder = new BasicAttributes(true); 291 holder.put(schemaAttr); 292 //System.err.println((String)schemaAttr.get()); 293 294 info.modifyAttributes(myEnv, DirContext.ADD_ATTRIBUTE, holder); 295 296 } 297 298 /** 299 * When we delete an entry, we use the original to make sure that 300 * any formatting inconsistencies are eliminated. 301 * This is because we're just deleting a value from an attribute 302 * on the server and there might not be any checks for extra spaces 303 * or parens. 304 */ 305 final private void deleteServerSchema(Attributes origAttrs) 306 throws NamingException { 307 308 Attribute origAttrVal; 309 310 switch (objectType) { 311 case OBJECTCLASS_ROOT: 312 origAttrVal = info.parser.stringifyObjDesc(origAttrs); 313 break; 314 315 case ATTRIBUTE_ROOT: 316 origAttrVal = info.parser.stringifyAttrDesc(origAttrs); 317 break; 318 319 case SYNTAX_ROOT: 320 origAttrVal = info.parser.stringifySyntaxDesc(origAttrs); 321 break; 322 323 case MATCHRULE_ROOT: 324 origAttrVal = info.parser.stringifyMatchRuleDesc(origAttrs); 325 break; 326 327 case SCHEMA_ROOT: 328 throw new SchemaViolationException( 329 "Cannot delete schema root"); 330 331 default: 332 throw new SchemaViolationException( 333 "Cannot delete child of schema object"); 334 } 335 336 ModificationItem[] mods = new ModificationItem[1]; 337 mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, origAttrVal); 338 339 info.modifyAttributes(myEnv, mods); 340 } 341 342 /** 343 * When we modify an entry, we use the original attribute value 344 * in the schema to make sure that any formatting inconsistencies 345 * are eliminated. A modification is done by deleting the original 346 * value and adding a new value with the modification. 347 */ 348 final private void modifyServerSchema(Attributes origAttrs, 349 Attributes newAttrs) throws NamingException { 350 351 Attribute newAttrVal; 352 Attribute origAttrVal; 353 354 switch (objectType) { 355 case OBJECTCLASS: 356 origAttrVal = info.parser.stringifyObjDesc(origAttrs); 357 newAttrVal = info.parser.stringifyObjDesc(newAttrs); 358 break; 359 360 case ATTRIBUTE: 361 origAttrVal = info.parser.stringifyAttrDesc(origAttrs); 362 newAttrVal = info.parser.stringifyAttrDesc(newAttrs); 363 break; 364 365 case SYNTAX: 366 origAttrVal = info.parser.stringifySyntaxDesc(origAttrs); 367 newAttrVal = info.parser.stringifySyntaxDesc(newAttrs); 368 break; 369 370 case MATCHRULE: 371 origAttrVal = info.parser.stringifyMatchRuleDesc(origAttrs); 372 newAttrVal = info.parser.stringifyMatchRuleDesc(newAttrs); 373 break; 374 375 default: 376 throw new SchemaViolationException( 377 "Cannot modify schema root"); 378 } 379 380 ModificationItem[] mods = new ModificationItem[2]; 381 mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, origAttrVal); 382 mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, newAttrVal); 383 384 info.modifyAttributes(myEnv, mods); 385 } 386 387 final static private class SchemaInfo { 388 private LdapCtx schemaEntry; 389 private String schemaEntryName; 390 LdapSchemaParser parser; 391 private String host; 392 private int port; 393 private boolean hasLdapsScheme; 394 395 SchemaInfo(String schemaEntryName, LdapCtx schemaEntry, 396 LdapSchemaParser parser) { 397 this.schemaEntryName = schemaEntryName; 398 this.schemaEntry = schemaEntry; 399 this.parser = parser; 400 this.port = schemaEntry.port_number; 401 this.host = schemaEntry.hostname; 402 this.hasLdapsScheme = schemaEntry.hasLdapsScheme; 403 } 404 405 synchronized void close() throws NamingException { 406 if (schemaEntry != null) { 407 schemaEntry.close(); 408 schemaEntry = null; 409 } 410 } 411 412 private LdapCtx reopenEntry(Hashtable env) throws NamingException { 413 // Use subschemasubentry name as DN 414 return new LdapCtx(schemaEntryName, host, port, 415 env, hasLdapsScheme); 416 } 417 418 synchronized void modifyAttributes(Hashtable env, ModificationItem[] mods) 419 throws NamingException { 420 if (schemaEntry == null) { 421 schemaEntry = reopenEntry(env); 422 } 423 schemaEntry.modifyAttributes("", mods); 424 } 425 426 synchronized void modifyAttributes(Hashtable env, int mod, 427 Attributes attrs) throws NamingException { 428 if (schemaEntry == null) { 429 schemaEntry = reopenEntry(env); 430 } 431 schemaEntry.modifyAttributes("", mod, attrs); 432 } 433 } 434 }