blob: f1616afc15070e9076cb3b34b8bc0eb14e1442d7 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1999-2003 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package com.sun.jndi.ldap;
27
28import javax.naming.*;
29import javax.naming.directory.*;
30import java.util.Hashtable;
31import 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
44final 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}