001: /*
002: * Copyright 1999-2003 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.jndi.ldap;
027:
028: import javax.naming.*;
029: import javax.naming.directory.*;
030: import java.util.Hashtable;
031: import com.sun.jndi.toolkit.dir.HierMemDirCtx;
032:
033: /**
034: * This is the class used to implement LDAP's GetSchema call.
035: *
036: * It subclasses HierMemDirContext for most of the functionality. It
037: * overrides functions that cause the schema definitions to change.
038: * In such a case, it write the schema to the LdapServer and (assuming
039: * there are no errors), calls it's superclass's equivalent function.
040: * Thus, the schema tree and the LDAP server's schema attributes are
041: * always in sync.
042: */
043:
044: final class LdapSchemaCtx extends HierMemDirCtx {
045:
046: static private final boolean debug = false;
047:
048: private static final int LEAF = 0; // schema object (e.g. attribute type defn)
049: private static final int SCHEMA_ROOT = 1; // schema tree root
050: static final int OBJECTCLASS_ROOT = 2; // root of object class subtree
051: static final int ATTRIBUTE_ROOT = 3; // root of attribute type subtree
052: static final int SYNTAX_ROOT = 4; // root of syntax subtree
053: static final int MATCHRULE_ROOT = 5; // root of matching rule subtree
054: static final int OBJECTCLASS = 6; // an object class definition
055: static final int ATTRIBUTE = 7; // an attribute type definition
056: static final int SYNTAX = 8; // a syntax definition
057: static final int MATCHRULE = 9; // a matching rule definition
058:
059: private SchemaInfo info = null;
060: private boolean setupMode = true;
061:
062: private int objectType;
063:
064: static DirContext createSchemaTree(Hashtable env,
065: String subschemasubentry, LdapCtx schemaEntry,
066: Attributes schemaAttrs, boolean netscapeBug)
067: throws NamingException {
068: try {
069: LdapSchemaParser parser = new LdapSchemaParser(netscapeBug);
070:
071: SchemaInfo allinfo = new SchemaInfo(subschemasubentry,
072: schemaEntry, parser);
073:
074: LdapSchemaCtx root = new LdapSchemaCtx(SCHEMA_ROOT, env,
075: allinfo);
076: parser.LDAP2JNDISchema(schemaAttrs, root);
077: return root;
078: } catch (NamingException e) {
079: schemaEntry.close(); // cleanup
080: throw e;
081: }
082: }
083:
084: // Called by createNewCtx
085: private LdapSchemaCtx(int objectType, Hashtable environment,
086: SchemaInfo info) {
087: super (environment, LdapClient.caseIgnore);
088:
089: this .objectType = objectType;
090: this .info = info;
091: }
092:
093: // override HierMemDirCtx.close to prevent premature GC of shared data
094: public void close() throws NamingException {
095: info.close();
096: }
097:
098: // override to ignore obj and use attrs
099: // treat same as createSubcontext
100: final public void bind(Name name, Object obj, Attributes attrs)
101: throws NamingException {
102: if (!setupMode) {
103: if (obj != null) {
104: throw new IllegalArgumentException("obj must be null");
105: }
106:
107: // Update server
108: addServerSchema(attrs);
109: }
110:
111: // Update in-memory copy
112: LdapSchemaCtx newEntry = (LdapSchemaCtx) super
113: .doCreateSubcontext(name, attrs);
114: }
115:
116: final protected void doBind(Name name, Object obj,
117: Attributes attrs, boolean useFactory)
118: throws NamingException {
119: if (!setupMode) {
120: throw new SchemaViolationException(
121: "Cannot bind arbitrary object; use createSubcontext()");
122: } else {
123: super .doBind(name, obj, attrs, false); // always ignore factories
124: }
125: }
126:
127: // override to use bind() instead
128: final public void rebind(Name name, Object obj, Attributes attrs)
129: throws NamingException {
130: try {
131: doLookup(name, false);
132: throw new SchemaViolationException(
133: "Cannot replace existing schema object");
134: } catch (NameNotFoundException e) {
135: bind(name, obj, attrs);
136: }
137: }
138:
139: final protected void doRebind(Name name, Object obj,
140: Attributes attrs, boolean useFactory)
141: throws NamingException {
142: if (!setupMode) {
143: throw new SchemaViolationException(
144: "Cannot bind arbitrary object; use createSubcontext()");
145: } else {
146: super .doRebind(name, obj, attrs, false); // always ignore factories
147: }
148: }
149:
150: final protected void doUnbind(Name name) throws NamingException {
151: if (!setupMode) {
152: // Update server
153: try {
154: // Lookup entry from memory
155: LdapSchemaCtx target = (LdapSchemaCtx) doLookup(name,
156: false);
157:
158: deleteServerSchema(target.attrs);
159: } catch (NameNotFoundException e) {
160: return;
161: }
162: }
163: // Update in-memory copy
164: super .doUnbind(name);
165: }
166:
167: final protected void doRename(Name oldname, Name newname)
168: throws NamingException {
169: if (!setupMode) {
170: throw new SchemaViolationException(
171: "Cannot rename a schema object");
172: } else {
173: super .doRename(oldname, newname);
174: }
175: }
176:
177: final protected void doDestroySubcontext(Name name)
178: throws NamingException {
179: if (!setupMode) {
180: // Update server
181: try {
182: // Lookup entry from memory
183: LdapSchemaCtx target = (LdapSchemaCtx) doLookup(name,
184: false);
185:
186: deleteServerSchema(target.attrs);
187: } catch (NameNotFoundException e) {
188: return;
189: }
190: }
191:
192: // Update in-memory copy
193: super .doDestroySubcontext(name);
194: }
195:
196: // Called to create oc, attr, syntax or matching rule roots and leaf entries
197: final LdapSchemaCtx setup(int objectType, String name,
198: Attributes attrs) throws NamingException {
199: try {
200: setupMode = true;
201: LdapSchemaCtx answer = (LdapSchemaCtx) super
202: .doCreateSubcontext(new CompositeName(name), attrs);
203:
204: answer.objectType = objectType;
205: answer.setupMode = false;
206: return answer;
207: } finally {
208: setupMode = false;
209: }
210: }
211:
212: final protected DirContext doCreateSubcontext(Name name,
213: Attributes attrs) throws NamingException {
214:
215: if (attrs == null || attrs.size() == 0) {
216: throw new SchemaViolationException(
217: "Must supply attributes describing schema");
218: }
219:
220: if (!setupMode) {
221: // Update server
222: addServerSchema(attrs);
223: }
224:
225: // Update in-memory copy
226: LdapSchemaCtx newEntry = (LdapSchemaCtx) super
227: .doCreateSubcontext(name, attrs);
228: return newEntry;
229: }
230:
231: final private static Attributes deepClone(Attributes orig)
232: throws NamingException {
233: BasicAttributes copy = new BasicAttributes(true);
234: NamingEnumeration attrs = orig.getAll();
235: while (attrs.hasMore()) {
236: copy.put((Attribute) ((Attribute) attrs.next()).clone());
237: }
238: return copy;
239: }
240:
241: final protected void doModifyAttributes(ModificationItem[] mods)
242: throws NamingException {
243: if (setupMode) {
244: super .doModifyAttributes(mods);
245: } else {
246: Attributes copy = deepClone(attrs);
247:
248: // Apply modifications to copy
249: applyMods(mods, copy);
250:
251: // Update server copy
252: modifyServerSchema(attrs, copy);
253:
254: // Update in-memory copy
255: attrs = copy;
256: }
257: }
258:
259: // we override this so the superclass creates the right kind of contexts
260: // Default is to create LEAF objects; caller will change after creation
261: // if necessary
262: final protected HierMemDirCtx createNewCtx() {
263: LdapSchemaCtx ctx = new LdapSchemaCtx(LEAF, myEnv, info);
264: return ctx;
265: }
266:
267: final private void addServerSchema(Attributes attrs)
268: throws NamingException {
269: Attribute schemaAttr;
270:
271: switch (objectType) {
272: case OBJECTCLASS_ROOT:
273: schemaAttr = info.parser.stringifyObjDesc(attrs);
274: break;
275:
276: case ATTRIBUTE_ROOT:
277: schemaAttr = info.parser.stringifyAttrDesc(attrs);
278: break;
279:
280: case SYNTAX_ROOT:
281: schemaAttr = info.parser.stringifySyntaxDesc(attrs);
282: break;
283:
284: case MATCHRULE_ROOT:
285: schemaAttr = info.parser.stringifyMatchRuleDesc(attrs);
286: break;
287:
288: case SCHEMA_ROOT:
289: throw new SchemaViolationException(
290: "Cannot create new entry under schema root");
291:
292: default:
293: throw new SchemaViolationException(
294: "Cannot create child of schema object");
295: }
296:
297: Attributes holder = new BasicAttributes(true);
298: holder.put(schemaAttr);
299: //System.err.println((String)schemaAttr.get());
300:
301: info.modifyAttributes(myEnv, DirContext.ADD_ATTRIBUTE, holder);
302:
303: }
304:
305: /**
306: * When we delete an entry, we use the original to make sure that
307: * any formatting inconsistencies are eliminated.
308: * This is because we're just deleting a value from an attribute
309: * on the server and there might not be any checks for extra spaces
310: * or parens.
311: */
312: final private void deleteServerSchema(Attributes origAttrs)
313: throws NamingException {
314:
315: Attribute origAttrVal;
316:
317: switch (objectType) {
318: case OBJECTCLASS_ROOT:
319: origAttrVal = info.parser.stringifyObjDesc(origAttrs);
320: break;
321:
322: case ATTRIBUTE_ROOT:
323: origAttrVal = info.parser.stringifyAttrDesc(origAttrs);
324: break;
325:
326: case SYNTAX_ROOT:
327: origAttrVal = info.parser.stringifySyntaxDesc(origAttrs);
328: break;
329:
330: case MATCHRULE_ROOT:
331: origAttrVal = info.parser.stringifyMatchRuleDesc(origAttrs);
332: break;
333:
334: case SCHEMA_ROOT:
335: throw new SchemaViolationException(
336: "Cannot delete schema root");
337:
338: default:
339: throw new SchemaViolationException(
340: "Cannot delete child of schema object");
341: }
342:
343: ModificationItem[] mods = new ModificationItem[1];
344: mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
345: origAttrVal);
346:
347: info.modifyAttributes(myEnv, mods);
348: }
349:
350: /**
351: * When we modify an entry, we use the original attribute value
352: * in the schema to make sure that any formatting inconsistencies
353: * are eliminated. A modification is done by deleting the original
354: * value and adding a new value with the modification.
355: */
356: final private void modifyServerSchema(Attributes origAttrs,
357: Attributes newAttrs) throws NamingException {
358:
359: Attribute newAttrVal;
360: Attribute origAttrVal;
361:
362: switch (objectType) {
363: case OBJECTCLASS:
364: origAttrVal = info.parser.stringifyObjDesc(origAttrs);
365: newAttrVal = info.parser.stringifyObjDesc(newAttrs);
366: break;
367:
368: case ATTRIBUTE:
369: origAttrVal = info.parser.stringifyAttrDesc(origAttrs);
370: newAttrVal = info.parser.stringifyAttrDesc(newAttrs);
371: break;
372:
373: case SYNTAX:
374: origAttrVal = info.parser.stringifySyntaxDesc(origAttrs);
375: newAttrVal = info.parser.stringifySyntaxDesc(newAttrs);
376: break;
377:
378: case MATCHRULE:
379: origAttrVal = info.parser.stringifyMatchRuleDesc(origAttrs);
380: newAttrVal = info.parser.stringifyMatchRuleDesc(newAttrs);
381: break;
382:
383: default:
384: throw new SchemaViolationException(
385: "Cannot modify schema root");
386: }
387:
388: ModificationItem[] mods = new ModificationItem[2];
389: mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
390: origAttrVal);
391: mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE,
392: newAttrVal);
393:
394: info.modifyAttributes(myEnv, mods);
395: }
396:
397: final static private class SchemaInfo {
398: private LdapCtx schemaEntry;
399: private String schemaEntryName;
400: LdapSchemaParser parser;
401: private String host;
402: private int port;
403: private boolean hasLdapsScheme;
404:
405: SchemaInfo(String schemaEntryName, LdapCtx schemaEntry,
406: LdapSchemaParser parser) {
407: this .schemaEntryName = schemaEntryName;
408: this .schemaEntry = schemaEntry;
409: this .parser = parser;
410: this .port = schemaEntry.port_number;
411: this .host = schemaEntry.hostname;
412: this .hasLdapsScheme = schemaEntry.hasLdapsScheme;
413: }
414:
415: synchronized void close() throws NamingException {
416: if (schemaEntry != null) {
417: schemaEntry.close();
418: schemaEntry = null;
419: }
420: }
421:
422: private LdapCtx reopenEntry(Hashtable env)
423: throws NamingException {
424: // Use subschemasubentry name as DN
425: return new LdapCtx(schemaEntryName, host, port, env,
426: hasLdapsScheme);
427: }
428:
429: synchronized void modifyAttributes(Hashtable env,
430: ModificationItem[] mods) throws NamingException {
431: if (schemaEntry == null) {
432: schemaEntry = reopenEntry(env);
433: }
434: schemaEntry.modifyAttributes("", mods);
435: }
436:
437: synchronized void modifyAttributes(Hashtable env, int mod,
438: Attributes attrs) throws NamingException {
439: if (schemaEntry == null) {
440: schemaEntry = reopenEntry(env);
441: }
442: schemaEntry.modifyAttributes("", mod, attrs);
443: }
444: }
445: }
|