/* * Copyright 2006-2012 The Scriptella Project Team. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package scriptella.driver.ldap; import scriptella.core.EtlCancelledException; import scriptella.driver.ldap.ldif.Entry; import scriptella.driver.ldap.ldif.LdifParseException; import scriptella.driver.ldap.ldif.LdifReader; import scriptella.driver.ldap.ldif.TrackingLineIterator; import scriptella.expression.LineIterator; import scriptella.spi.AbstractConnection; import scriptella.spi.ParametersCallback; import javax.naming.CompoundName; import javax.naming.Name; import javax.naming.NamingException; import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.directory.ModificationItem; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.util.List; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; /** * Executor for LDIF script. * * @author Fyodor Kupolov * @version 1.0 */ public class LdifScript { private static final Logger LOG = Logger.getLogger(LdifScript.class.getName()); //Parsing options for DN, TODO maybe replace with a DirCtx name parser? static final Properties DN_SYNTAX = new Properties(); static { DN_SYNTAX.setProperty("jndi.syntax.direction", "right_to_left"); DN_SYNTAX.setProperty("jndi.syntax.separator", ","); DN_SYNTAX.setProperty("jndi.syntax.ignorecase", "true"); DN_SYNTAX.setProperty("jndi.syntax.trimblanks", "true"); } private final LdapConnection connection; public LdifScript(LdapConnection connection) { if (connection == null) { throw new IllegalArgumentException("Connection cannot be null"); } this.connection = connection; } /** * Executes an LDIF content from the specified reader. * * @param reader reader with LDIF content. * @throws LdapProviderException if directory access failed. */ public void execute(Reader reader, DirContext ctx, ParametersCallback parameters) throws LdapProviderException { if (reader == null) { throw new IllegalArgumentException("Reader cannot be null"); } if (ctx == null) { throw new IllegalArgumentException("DirContext cannot be null"); } if (parameters == null) { throw new IllegalArgumentException("Parameters cannot be null"); } TrackingLineIterator in = new TrackingLineIterator(reader, parameters); AbstractConnection.StatementCounter counter = connection.getStatementCounter(); try { in.trackLines(); for (LdifIterator it = newLdifIterator(in); it.hasNext(); in.trackLines()) { EtlCancelledException.checkEtlCancelled(); Entry e = it.next(); if (isReadonly()) { LOG.info("Readonly Mode - " + e + " has been skipped."); } else { modify(ctx, e); } counter.statements++; } } catch (LdifParseException e) { if (e.getErrorStatement() == null) { e.setErrorStatement(in.getTrackedLines()); } throw e; } catch (NamingException e) { LdapProviderException ex = new LdapProviderException("Failed to execute LDIF entry", e); ex.setErrorStatement(in.getTrackedLines()); throw ex; } } /** * Adds/modifies ctx using entry information. * * @param ctx directory context to use for change. * @param e entry with change description. * @throws NamingException if operation with directory failed. */ static void modify(DirContext ctx, final Entry e) throws NamingException { if (LOG.isLoggable(Level.FINE)) { LOG.fine("Processing " + e); } Attributes atts = e.getAttributes(); final String rootDn = ctx.getNameInNamespace(); if (atts != null) { //If add entry ctx.createSubcontext(getRelativeDN(rootDn, e.getDn()), e.getAttributes()); } else if (e.isChangeDelete()) { ctx.destroySubcontext(getRelativeDN(rootDn, e.getDn())); } else if (e.isChangeModDn() || e.isChangeModRdn()) { Name newRdn; if (e.getNewSuperior() != null) { //If new superior newRdn = getRelativeDN(rootDn, e.getNewSuperior()); } else { //otherwise use DN as a base newRdn = getRelativeDN(rootDn, e.getDn()); newRdn.remove(newRdn.size() - 1); } newRdn.add(e.getNewRdn()); ctx.addToEnvironment("java.naming.ldap.deleteRDN", String.valueOf(e.isDeleteOldRdn())); ctx.rename(getRelativeDN(rootDn, e.getDn()), newRdn); ctx.removeFromEnvironment("java.naming.ldap.deleteRDN");//a better solution to use the previous value } else { List<ModificationItem> items = e.getModificationItems(); ctx.modifyAttributes(getRelativeDN(rootDn, e.getDn()), items.toArray(new ModificationItem[items.size()])); } } /** * @param rootDn root context DN. * @param dn DN to compute a relative name. DN must starts with rootDn. * @return name relative to a root context DN. */ static Name getRelativeDN(final String rootDn, final String dn) throws NamingException { CompoundName root = new CompoundName(rootDn, DN_SYNTAX); CompoundName entry = new CompoundName(dn, DN_SYNTAX); if (!entry.startsWith(root)) { throw new NamingException("Dn " + dn + " is not from root DN " + rootDn); } return entry.getSuffix(root.size()); } /** * Accessor for testing purposes */ protected Long getMaxFileLength() { return connection == null ? null : connection.getMaxFileLength(); } protected boolean isReadonly() { return connection != null && connection.isReadonly(); } private LdifIterator newLdifIterator(LineIterator in) { Long mx = getMaxFileLength(); return mx == null ? new LdifIterator(in) : new LdifIterator(in, mx); } private class LdifIterator extends LdifReader { public LdifIterator(LineIterator in, long sizeLimit) { super(in, sizeLimit); } public LdifIterator(LineIterator in) { super(in); } protected InputStream getUriStream(String uri) throws IOException { return connection.getDriversContext().resolve(uri).openStream(); } } }