3667N/A * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. 0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 0N/A * This code is free software; you can redistribute it and/or modify it 0N/A * under the terms of the GNU General Public License version 2 only, as 2362N/A * published by the Free Software Foundation. Oracle designates this 0N/A * particular file as subject to the "Classpath" exception as provided 2362N/A * by Oracle in the LICENSE file that accompanied this code. 0N/A * This code is distributed in the hope that it will be useful, but WITHOUT 0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 0N/A * version 2 for more details (a copy is included in the LICENSE file that 0N/A * accompanied this code). 0N/A * You should have received a copy of the GNU General Public License version 0N/A * 2 along with this work; if not, write to the Free Software Foundation, 0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2362N/A * or visit www.oracle.com if you need additional information or have any 0N/A * The LDAP context implementation. 0N/A * Implementation is not thread-safe. Caller must sync as per JNDI spec. 0N/A * Members that are used directly or indirectly by internal worker threads 0N/A * (Connection, EventQueue, NamingEventNotifier) must be thread-safe. 0N/A * Connection - calls LdapClient.processUnsolicited(), which in turn calls 0N/A * LdapCtx.convertControls() and LdapCtx.fireUnsolicited(). 0N/A * convertControls() - no sync; reads envprops and 'this' 0N/A * fireUnsolicited() - sync on eventSupport for all references to 'unsolicited' 0N/A * (even those in other methods); don't sync on LdapCtx in case caller 0N/A * is already sync'ing on it - this would prevent Unsol events from firing 0N/A * and the Connection thread to block (thus preventing any other data 0N/A * from being read from the connection) 0N/A * References to 'eventSupport' need not be sync'ed because these 0N/A * methods can only be called after eventSupport has been set first 0N/A * (via addNamingListener()). 0N/A * EventQueue - no direct or indirect calls to LdapCtx 0N/A * NamingEventNotifier - calls newInstance() to get instance for run() to use; 0N/A * no sync needed for methods invoked on new instance; 0N/A * LdapAttribute links to LdapCtx in order to process getAttributeDefinition() 0N/A * and getAttributeSyntaxDefinition() calls. It invokes LdapCtx.getSchema(), 0N/A * which uses schemaTrees (a Hashtable - already sync). Potential conflict 0N/A * of duplicating construction of tree for same subschemasubentry 0N/A * but no inconsistency problems. 0N/A * NamingEnumerations link to LdapCtx for the following: 0N/A * underlying connection 0N/A * 2. LdapClient handle to get next batch of results 0N/A * 3. Sets LdapCtx's response controls 0N/A * 4. Process return code 0N/A * 5. For narrowing response controls (using ctx's factories) 0N/A * Since processing of NamingEnumeration by client is treated the same as method 0N/A * invocation on LdapCtx, caller is responsible for locking. 0N/A * @author Vincent Ryan 0N/A * @author Rosanna Lee 0N/A * Used to store arguments to the search method. 0N/A private static final boolean debug =
false;
0N/A // ----------------- Constants ----------------- 0N/A // Used by LdapPoolManager 0N/A "javax.net.ssl.SSLSocketFactory";
// use Sun's SSL 0N/A // schema operational and user attributes 0N/A {
"objectClasses",
"attributeTypes",
"matchingRules",
"ldapSyntaxes" };
0N/A // --------------- Environment property names ---------- 0N/A // LDAP protocol version: "2", "3" 0N/A // Binary-valued attributes. Space separated string of attribute names. 0N/A "java.naming.ldap.attributes.binary";
0N/A // Delete old RDN during modifyDN: "true", "false" 0N/A // De-reference aliases: "never", "searching", "finding", "always" 0N/A // Return only attribute types (no values) 0N/A // Separator character for encoding Reference's RefAddrs; default is '#' 0N/A // Bind Controls (used by LdapReferralException) 0N/A "java.naming.ldap.referral.limit";
0N/A // trace BER (java.io.OutputStream) 0N/A // Get around Netscape Schema Bugs 0N/A "com.sun.jndi.ldap.netscape.schemaBugs";
0N/A "com.sun.naming.netscape.schemaBugs";
// for backward compatability 0N/A // Timeout for socket connect 0N/A "com.sun.jndi.ldap.connect.timeout";
0N/A // Timeout for reading responses 0N/A "com.sun.jndi.ldap.read.timeout";
0N/A // Environment property for connection pooling 0N/A // Environment property for the domain name (derived from this context's DN) 3667N/A // Block until the first search reply is received 3667N/A "com.sun.jndi.ldap.search.waitForReply";
3667N/A // Size of the queue of unprocessed search replies 3667N/A "com.sun.jndi.ldap.search.replyQueueSize";
0N/A // ----------------- Fields that don't change ----------------------- 0N/A // controls that Provider needs 0N/A // ------------ Package private instance variables ---------------- 0N/A // Cannot be private; used by enums 0N/A // ------- Inherited by derived context instances 0N/A // for IPv6 literals) 0N/A // using an LDAPS URL. 0N/A // ------- Not inherited by derived context instances 0N/A // ------------- Private instance variables ------------------------ 0N/A // ------- Inherited by derived context instances 0N/A private boolean useSsl =
false;
// true if SSL protocol is active 0N/A // ------- Not inherited by derived context instances 0N/A // True if this context was created by another LdapCtx. 0N/A private int hopCount =
1;
// current referral hop count 0N/A private boolean unsolicited =
false;
// if there unsolicited listeners 0N/A private boolean sharable =
true;
// can share connection with other ctx 0N/A // -------------- Constructors ----------------------------------- 0N/A // SSL env prop overrides the useSsl argument 0N/A // %%% These are only examined when the context is created 0N/A // %%% because they are only for debugging or workaround purposes. 0N/A // Connection controls are inherited from environment 0N/A // Set clone's request controls 0N/A // setRequestControls() will clone reqCtls 0N/A // --------------- Namespace Updates --------------------- 0N/A * if obj is DirContext, attrs = obj.getAttributes() 0N/A * if attrs == null && obj == null 0N/A * disallow (cannot determine objectclass to use) 0N/A * just create entry using attrs 0N/A * objAttrs = create attributes for representing obj 0N/A * create entry using attrs 0N/A "cannot bind null object with no attributes");
0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A * if obj is DirContext, attrs = obj.getAttributes(). 0N/A * leave any existing attributes alone 0N/A * (set attrs = {objectclass=top} if object doesn't exist) 0N/A * replace all existing attributes with attrs 0N/A * just create entry using attrs 0N/A * objAttrs = create attributes for representing obj 0N/A * create entry using attrs 0N/A // Check if name is bound 0N/A // Name not bound, just add it 0N/A // there's an object there already, need to figure out 0N/A // what to do about its attributes 0N/A // we're not changing any attrs, leave old attributes alone 0N/A // Remove Java-related object classes from objectclass attribute 0N/A // clone so that keepAttrs is not affected 0N/A // remove all Java-related attributes except objectclass 0N/A // add it back using updated attrs 0N/A // Attempt to restore old entry 0N/A // Rethrow exception 0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A // assert (oldName instanceOf CompositeName); 0N/A // permit oldName to be empty (for processing referral contexts) 0N/A "LDAPv2 doesn't support changing " +
0N/A "the parent as a result of a rename");
0N/A // Record the new RDN (for use after the referral is followed). 0N/A // Cannot continue when a referral has been received and a 0N/A // newSuperior name was supplied (because the newSuperior is 0N/A // relative to a naming context BEFORE the referral is followed). 0N/A "Cannot continue referral processing when newSuperior is " +
0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A // add structural objectclass; name needs to have "cn" 0N/A // creation successful, get back live object 0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A * Adds attributes from RDN to attrs if not already present. 0N/A * Note that if attrs already contains an attribute by the same name, 0N/A * or if the distinguished name is empty, then leave attrs unchanged. 0N/A * @param dn The non-null DN of the entry to add 0N/A * @param attrs The non-null attributes of entry to add 0N/A * @param directUpdate Whether attrs can be updated directly 0N/A * @returns Non-null attributes with attributes from the RDN added 0N/A // Handle the empty name 0N/A // Parse string name into list of RDNs 0N/A //List<Rdn> rdnList = (new LdapName(dn)).rdns(); 0N/A //Rdn rdn = rdnList.get(rdnList.size() - 1); 0N/A // Add attributes of RDN to attrs if not already there 0N/A // If attrs already has the attribute, don't change or add to it 0N/A * When attrs.isCaseIgnored() is false, attrs.get() will 0N/A * return null when the case mis-matches for otherwise 0N/A * As the attrIDs' case is irrelevant for LDAP, ignore 0N/A * the case of attrIDs even when attrs.isCaseIgnored() is 0N/A * false. This is done by explicitly comparing the elements in 0N/A * the enumeration of IDs with their case ignored. 0N/A // %%% RL: are there any implications for referrals? 0N/A * Append the the second Vector onto the first Vector 0N/A * (v2 must be non-null) 0N/A // ------------- Lookups and Browsing ------------------------- 0N/A // should get back 1 SearchResponse and 1 SearchResult 0N/A // found it but got no attributes 0N/A // serialized object or object reference 0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A "problem generating object using object factory");
0N/A // set this flag to override the typesOnly flag 0N/A // list result may contain continuation references 0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A // listBindings result may contain continuation references 0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A // --------------- Name-related Methods ----------------------- 0N/A // ignore name, always return same parser 0N/A // Handle compound names. A pair of LdapNames is an easy case. 0N/A // used by LdapSearchEnumeration 0N/A // --------------- Reading and Updating Attributes 0N/A // get attributes from result 0N/A // do this so attributes can find their schema 0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A return;
// nothing to do 0N/A // construct mod list 0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A return;
// nothing to do 0N/A // construct mod list 0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A // ------------------- Schema ----------------------- 0N/A // retrieve the objectClass attribute from LDAP 0N/A // retrieve the root of the ObjectClass schema tree 0N/A // create a context to hold the schema objects representing the object 0N/A // %%% Should we fail if not found, or just continue? 0N/A // Make context read-only 0N/A * getSchemaTree first looks to see if we have already built a 0N/A * schema tree for the given entry. If not, it builds a new one and 0N/A * stores it in our private hash table 0N/A * buildSchemaTree builds the schema tree corresponding to the 0N/A * given subschemasubentree 0N/A // get the schema entry itself 0N/A // DO ask for return object here because we need it to 0N/A // create context. Since asking for all attrs, we won't 0N/A // be transmitting any specific attrIDs (like Java-specific ones). 0N/A 0,
0,
/* count and time limits */ 0N/A true /* return obj */,
0N/A false /*deref link */ );
0N/A * getSchemaEntree returns the DN of the subschemasubentree for the 0N/A * given entree. It first looks to see if the given entry has 0N/A * a subschema different from that of the root DIT (by looking for 0N/A * a "subschemasubentry" attribute). If it doesn't find one, it returns 0N/A * the one for the root of the DIT (by looking for the root's 0N/A * "subschemasubentry" attribute). 0N/A * This function is called regardless of the server's version, since 0N/A * an administrator may have setup the server to support client schema 0N/A * queries. If this function trys a serarch on a v2 server that 0N/A * doesn't support schema, one of these two things will happen: 0N/A * 1) It will get an exception when querying the root DSE 0N/A * 2) It will not find a subschemasubentry on the root DSE 0N/A * If either of these things occur and the server is not v3, we 0N/A * throw OperationNotSupported. 0N/A * the relative flag tells whether the given name is relative to this 0N/A // Asks for operational attribute "subschemasubentry" 0N/A 0,
0,
/* count and time limits */ 0N/A new String[]{
"subschemasubentry"}
/* attr to return */,
0N/A false /* returning obj */,
0N/A false /* deref link */);
0N/A // we got an error looking for a root entry on an ldapv2 0N/A // server. The server must not support schema. 0N/A "Cannot get schema information from server");
0N/A "Requesting schema of nonexistent entry: " +
name);
0N/A //System.err.println("schema entry attrs: " + schemaEntryAttr); 0N/A // the server doesn't have a subschemasubentry in its root DSE. 0N/A // therefore, it doesn't support schema. 0N/A "Cannot read subschemasubentry of root DSE");
0N/A // package-private; used by search enum. 0N/A // Set attributes to point to this context in case some one 0N/A // asked for their schema 0N/A * Returns the URL associated with this context; used by LdapAttribute 0N/A * after deserialization to get pointer to this context. 0N/A // --------------------- Searches ----------------------------- 0N/A // Used by NamingNotifier 0N/A // if objects are requested then request the Java attributes too 0N/A // so that the objects can be constructed 0N/A // check for presence of "*" (user attributes wildcard) 0N/A // see if this can be done as a compare, otherwise do a search 0N/A //System.err.println("compare triggered"); 0N/A // search result may contain referrals 0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A // ensureOpen() won't work here because 0N/A // session was associated with previous connection 0N/A // %%% RL: we can actually allow the enumeration to continue 0N/A // using the old handle but other weird things might happen 0N/A // when we hit a referral 0N/A "Context's connection changed; unable to continue enumeration");
0N/A // Perform a search. Expect 1 SearchResultEntry and the SearchResultDone. 0N/A // If cons.getReturningObjFlag() then caller should already 0N/A // have make sure to request the appropriate attrs 0N/A // Ldap treats null and empty array the same 0N/A // need to replace with single element array 0N/A // JNDI unit is milliseconds, LDAP unit is seconds. 0N/A // Zero means no limit. 0N/A * Certain simple JNDI searches are automatically converted to 0N/A * LDAP compare operations by the LDAP service provider. A search 0N/A * is converted to a compare iff: 0N/A * - the scope is set to OBJECT_SCOPE 0N/A * - the filter string contains a simple assertion: "<type>=<value>" 0N/A * - the returning attributes list is present but empty 0N/A // returns true if a search can be caried out as a compare, and sets 0N/A // tokens[0] and tokens[1] to the type and value respectively. 0N/A // e.g. filter "cn=Jon Ruiz" becomes, type "cn" and value "Jon Ruiz" 0N/A // This function uses the documents JNDI Compare example as a model 0N/A // for when to turn a search into a compare. 0N/A // if scope is not object-scope, it's really a search 0N/A // if attributes are to be returned, it's really a search 0N/A // if the filter not a simple assertion, it's really a search 0N/A // it can be converted to a compare 0N/A // If the supplied filter is a simple assertion i.e. "<type>=<value>" 0N/A // (enclosing parentheses are permitted) then 0N/A // filterToAssertion will return true and pass the type and value as 0N/A // the first and second elements of tokens respectively. 0N/A // precondition: tokens[] must be initialized and be at least of size 2. 0N/A // find the left and right half of the assertion 0N/A // make sure the value does not contain a wildcard 0N/A // test for enclosing parenthesis 0N/A return false;
// unbalanced 0N/A // make sure the left and right half are not expresions themselves 0N/A // strip off enclosing parenthesis, if present 0N/A // -------------- Environment Properties ------------------ 0N/A * Override with noncloning version. 0N/A // not there; just return 0N/A// The following properties affect the connection 0N/A // De-activate SSL and reset the context's url and port number 0N/A // Update environment; reconnection will use new props 0N/A // If adding null, call remove 0N/A// The following properties affect the connection 0N/A // Activate SSL and reset the context's url and port number 0N/A // Update environment; reconnection will use new props 0N/A * Sets the URL that created the context in the java.naming.provider.url 0N/A * Sets the domain name for the context in the com.sun.jndi.ldap.domainname 0N/A * Used for hostname verification by Start TLS 0N/A // Make sure that referrals are to their default 0N/A // Set separator used for encoding RefAddr 0N/A // Set whether RDN is removed when renaming object 0N/A // Set whether types are returned only 0N/A // Set how aliases are dereferenced 0N/A // Set the limit on referral chains 0N/A // set referral handling 0N/A // Set the connect timeout 0N/A // Set the read timeout 3667N/A // Set the flag that controls whether to block until the first reply 3667N/A // Set the size of the queue of unprocessed search replies 0N/A // When connection is created, it will use these and other 0N/A // properties from the environment 0N/A * Sets the batch size of this context; 0N/A * Sets the referral mode of this context to 'follow', 'throw' or 'ignore'. 0N/A * If referral mode is 'ignore' then activate the manageReferral control. 0N/A // First determine the referral mode 0N/A // If ignoring referrals, add manageReferralControl 0N/A // If we're update an existing context, remove the control 0N/A }
// else, leave alone; need not update 0N/A * Set whether aliases are derefereced during resolution and searches. 0N/A * Sets the limit on referral chains 0N/A // set referral limit 0N/A // a zero setting indicates no limit 0N/A // For counting referral hops 0N/A * Sets the connect timeout value 3667N/A * Sets the size of the queue of unprocessed search replies 3667N/A * Sets the flag that controls whether to block until the first search 0N/A * Sets the read timeout value 0N/A * Extract URLs from a string. The format of the string is: 0N/A * <urlstring > ::= "Referral:" <ldapurls> 0N/A * <ldapurls> ::= <separator> <ldapurl> | <ldapurls> 0N/A * <separator> ::= ASCII linefeed character (0x0a) 0N/A * <ldapurl> ::= LDAP URL format (RFC 1959) 0N/A // count the number of URLs 0N/A * Argument is a space-separated list of attribute IDs 0N/A * Converts attribute IDs to lowercase before adding to built-in list. 0N/A // ----------------- Connection --------------------- 0N/A // Event (normal and unsolicited) 0N/A // Enumerations that are keeping the connection alive 0N/A// %%%: RL: There is no need to set these to null, as they're just 0N/A// variables whose contents and references will automatically 0N/A// be cleaned up when they're no longer referenced. 0N/A// Also, setting these to null creates problems for the attribute 0N/A// schema-related methods, which need these to work. 0N/A // Update environment 0N/A sharable =
false;
// can't share with existing contexts 0N/A // reset the cache before a new connection is established 0N/A // reset the cache before a new connection is established 0N/A sharable =
true;
// connection is now either new or single-use 0N/A // OK for others to start sharing again 0N/A boolean usePool =
false;
// enable connection pooling 0N/A // Required for LdapClient constructor 0N/A // Required for basic client identity 0N/A // Required for simple client identity 0N/A // Required for SASL client identity 0N/A * Pooled connections are preauthenticated; 0N/A * newly created ones are not. 0N/A return;
// no authentication required 0N/A // reauthenticating over existing connection; 0N/A // only v3 supports this 0N/A // Process the referrals sequentially (top level) and 0N/A // recursively (per referral) 0N/A // No more referrals to follow 0N/A // No saved exception, something must have gone wrong 0N/A "Internal error processing referral during connection");
0N/A continue;
// follow another referral 0N/A // Used by Enum classes to track whether it still needs context 0N/A // ------------ Return code and Error messages ----------------------- 0N/A // handle Search continuation references 0N/A msg =
"Unprocessed Continuation Reference(s)";
0N/A // handle multiple sets of URLs 0N/A msg =
"Continuation Reference";
0N/A // make a chain of LdapReferralExceptions 0N/A // check the hop limit 0N/A // only one set of URLs is present 0N/A // check the hop limit 0N/A * Handle SLAPD-style referrals. 0N/A * Referrals received during name resolution should be followed 0N/A * until one succeeds - the target entry is located. An exception 0N/A * is thrown now to handle these. 0N/A * Referrals received during a search operation point to unexplored 0N/A * parts of the directory and each should be followed. An exception 0N/A * is thrown later (during results enumeration) to handle these. 0N/A // extract SLAPD-style referrals from errorMessage 0N/A * SLAPD-style referrals received during name resolution 0N/A * cannot be distinguished from those received during a 0N/A * search operation. Since both must be handled differently 0N/A * the following rule is applied: 0N/A * If 1 referral and 0 entries is received then 0N/A * assume name resolution has not yet completed. 0N/A // check the hop limit 0N/A * Maps an LDAP error code to an appropriate NamingException. 0N/A * %%% public; used by controls 0N/A * @param errorCode numeric LDAP error code 0N/A * @param errorMessage textual description of the LDAP error. May be null. 0N/A * @return A NamingException or null if the error code indicates success. 0N/A // %%% need new exception ? 0N/A // these are really not exceptions and this code probably 0N/A // never gets executed 0N/A // ----------------- Extensions and Controls ------------------- 0N/A // %%% verify request.getID() == answer.extensionId 0N/A // Pass the connection handle to StartTlsResponseImpl 0N/A // process the referrals sequentially 0N/A // repeat the original operation at the new context 0N/A // Make sure we close referral context 0N/A * Narrow controls using own default factory and ControlFactory. 0N/A * @param ctls A non-null Vector 0N/A // Try own factory first 0N/A // Try assigned factories if own produced null 0N/A // -------------------- Events ------------------------ 0N/A * Access to eventSupport need not be synchronized even though the 0N/A * Connection thread can access it asynchronously. It is 0N/A * impossible for a race condition to occur because 0N/A * eventSupport.addNamingListener() must have been called before 0N/A * the Connection thread can call back to this ctx. 0N/A // If first time asking for unsol 0N/A return;
// no activity before, so just return 0N/A // If removing an Unsol listener and it is the last one, let clnt know 0N/A // If first time asking for unsol 0N/A * Retrieves the target name for which the listener is registering. 0N/A * If nm is a CompositeName, use its first and only component. It 0N/A * cannot have more than one components because a target be outside of 0N/A * this namespace. If nm is not a CompositeName, then treat it as a 0N/A * @param nm The non-null target name. 0N/A "Target cannot span multiple namespaces: " +
nm);
0N/A // treat as compound name 0N/A // ------------------ Unsolicited Notification --------------- 0N/A // package private methods for handling unsolicited notification 0N/A * Registers this context with the underlying LdapClient. 0N/A * When the underlying LdapClient receives an unsolicited notification, 0N/A * it will invoke LdapCtx.fireUnsolicited() so that this context 0N/A * can (using EventSupport) notified any registered listeners. 0N/A * This method is called by EventSupport when an unsolicited listener 0N/A * first registers with this context (should be called just once). 0N/A * @see #removeUnsolicited 0N/A * @see #fireUnsolicited 0N/A // addNamingListener must have created EventSupport already 0N/A * Removes this context from registering interest in unsolicited 0N/A * notifications from the underlying LdapClient. This method is called 0N/A * under any one of the following conditions: 0N/A * <li>All unsolicited listeners have been removed. (see removingNamingListener) 0N/A * <li>This context is closed. 0N/A * <li>This context's underlying LdapClient changes. 0N/A * After this method has been called, this context will not pass 0N/A * on any events related to unsolicited notifications to EventSupport and 0N/A * and its listeners. 0N/A // addNamingListener must have created EventSupport already 0N/A * Uses EventSupport to fire an event related to an unsolicited notification. 0N/A * Called by LdapClient when LdapClient receives an unsolicited notification. 0N/A // addNamingListener must have created EventSupport already 0N/A // No need to notify clnt because clnt is the 0N/A // only one that can fire a NamingException to 0N/A // unsol listeners and it will handle its own cleanup