001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2009-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS
026 */
027package org.opends.guitools.controlpanel.util;
028
029import java.util.HashSet;
030import java.util.Set;
031
032import javax.naming.NamingEnumeration;
033import javax.naming.NamingException;
034import javax.naming.directory.SearchControls;
035import javax.naming.directory.SearchResult;
036import javax.naming.ldap.InitialLdapContext;
037
038import org.forgerock.opendj.config.server.ConfigException;
039import org.forgerock.opendj.ldap.ByteStringBuilder;
040import org.forgerock.opendj.ldap.ResultCode;
041import org.forgerock.opendj.ldap.schema.CoreSchema;
042import org.forgerock.opendj.ldap.schema.MatchingRule;
043import org.forgerock.opendj.ldap.schema.MatchingRuleImpl;
044import org.forgerock.opendj.ldap.schema.SchemaBuilder;
045import org.opends.guitools.controlpanel.browser.BrowserController;
046import org.opends.guitools.controlpanel.datamodel.CustomSearchResult;
047import org.opends.server.api.AttributeSyntax;
048import org.opends.server.config.ConfigConstants;
049import org.opends.server.core.DirectoryServer;
050import org.opends.server.core.ServerContext;
051import org.opends.server.replication.plugin.HistoricalCsnOrderingMatchingRuleImpl;
052import org.opends.server.schema.AciSyntax;
053import org.opends.server.schema.AttributeTypeSyntax;
054import org.opends.server.schema.LDAPSyntaxDescriptionSyntax;
055import org.opends.server.schema.ObjectClassSyntax;
056import org.opends.server.schema.SchemaConstants;
057import org.opends.server.schema.SubtreeSpecificationSyntax;
058import org.opends.server.types.DirectoryException;
059import org.opends.server.types.InitializationException;
060import org.opends.server.types.LDAPSyntaxDescription;
061import org.opends.server.types.Schema;
062
063/** Class used to retrieve the schema from the schema files. */
064public class RemoteSchemaLoader extends SchemaLoader
065{
066  private Schema schema;
067
068  /**
069   * In remote mode we cannot load the matching rules and syntaxes from local
070   * configuration, so we should instead bootstrap them from the SDK's core schema.
071   */
072  public RemoteSchemaLoader()
073  {
074    matchingRulesToKeep.clear();
075    syntaxesToKeep.clear();
076    matchingRulesToKeep.addAll(org.forgerock.opendj.ldap.schema.Schema.getCoreSchema().getMatchingRules());
077    syntaxesToKeep.addAll(org.forgerock.opendj.ldap.schema.Schema.getCoreSchema().getSyntaxes());
078  }
079  /**
080   * Reads the schema.
081   *
082   * @param ctx
083   *          the connection to be used to load the schema.
084   * @throws NamingException
085   *           if an error occurs reading the schema.
086   * @throws DirectoryException
087   *           if an error occurs parsing the schema.
088   * @throws InitializationException
089   *           if an error occurs finding the base schema.
090   * @throws ConfigException
091   *           if an error occurs loading the configuration required to use the
092   *           schema classes.
093   */
094  public void readSchema(InitialLdapContext ctx) throws NamingException, DirectoryException, InitializationException,
095      ConfigException
096  {
097    final String[] schemaAttrs = { ConfigConstants.ATTR_LDAP_SYNTAXES_LC, ConfigConstants.ATTR_ATTRIBUTE_TYPES_LC,
098      ConfigConstants.ATTR_OBJECTCLASSES_LC };
099    final SearchControls searchControls = new SearchControls();
100    searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
101    searchControls.setReturningAttributes(schemaAttrs);
102    final NamingEnumeration<SearchResult> srs = ctx.search(
103        ConfigConstants.DN_DEFAULT_SCHEMA_ROOT, BrowserController.ALL_OBJECTS_FILTER, searchControls);
104    SearchResult sr = null;
105    try
106    {
107      while (srs.hasMore())
108      {
109        sr = srs.next();
110      }
111    }
112    finally
113    {
114      srs.close();
115    }
116
117    final CustomSearchResult csr = new CustomSearchResult(sr, ConfigConstants.DN_DEFAULT_SCHEMA_ROOT);
118    schema = getBaseSchema();
119    // Add missing matching rules and attribute syntaxes to base schema to allow read of remote server schema
120    // (see OPENDJ-1122 for more details)
121    addMissingSyntaxesToBaseSchema(new AciSyntax(), new SubtreeSpecificationSyntax());
122    addMissingMatchingRuleToBaseSchema("1.3.6.1.4.1.26027.1.4.4", "historicalCsnOrderingMatch",
123        "1.3.6.1.4.1.1466.115.121.1.40", new HistoricalCsnOrderingMatchingRuleImpl());
124    for (final String str : schemaAttrs)
125    {
126      registerSchemaAttr(csr, str);
127    }
128  }
129
130  private void addMissingSyntaxesToBaseSchema(final AttributeSyntax<?>... syntaxes)
131      throws DirectoryException, InitializationException, ConfigException
132  {
133    for (AttributeSyntax<?> syntax : syntaxes)
134    {
135      final ServerContext serverContext = DirectoryServer.getInstance().getServerContext();
136      if (!serverContext.getSchemaNG().hasSyntax(syntax.getOID()))
137      {
138        syntax.initializeSyntax(null, serverContext);
139      }
140      schema.registerSyntax(syntax.getSDKSyntax(serverContext.getSchemaNG()), true);
141    }
142  }
143
144  private void addMissingMatchingRuleToBaseSchema(final String oid, final String name, final String syntaxOID,
145      final MatchingRuleImpl impl)
146      throws InitializationException, ConfigException, DirectoryException
147  {
148    final MatchingRule matchingRule;
149    if (CoreSchema.getInstance().hasMatchingRule(name))
150    {
151      matchingRule = CoreSchema.getInstance().getMatchingRule(name);
152    }
153    else
154    {
155      matchingRule = new SchemaBuilder(CoreSchema.getInstance()).buildMatchingRule(oid)
156                                                                .names(name)
157                                                                .syntaxOID(syntaxOID)
158                                                                .implementation(impl)
159                                                                .addToSchema().toSchema().getMatchingRule(oid);
160    }
161    schema.registerMatchingRule(matchingRule, true);
162  }
163
164  private void registerSchemaAttr(final CustomSearchResult csr, final String schemaAttr) throws DirectoryException
165  {
166    final Set<Object> remainingAttrs = new HashSet<>(csr.getAttributeValues(schemaAttr));
167    if (schemaAttr.equals(ConfigConstants.ATTR_LDAP_SYNTAXES_LC))
168    {
169      registerSchemaLdapSyntaxDefinitions(remainingAttrs);
170      return;
171    }
172
173    while (!remainingAttrs.isEmpty())
174    {
175      DirectoryException lastException = null;
176      final Set<Object> registered = new HashSet<>();
177      for (final Object definition : remainingAttrs)
178      {
179        final ByteStringBuilder sb = new ByteStringBuilder();
180        sb.appendObject(definition);
181        try
182        {
183          switch (schemaAttr)
184          {
185          case ConfigConstants.ATTR_ATTRIBUTE_TYPES_LC:
186            schema.registerAttributeType(AttributeTypeSyntax.decodeAttributeType(sb, schema, false), true);
187            break;
188          case ConfigConstants.ATTR_OBJECTCLASSES_LC:
189            schema.registerObjectClass(ObjectClassSyntax.decodeObjectClass(sb, schema, false), true);
190            break;
191          }
192          registered.add(definition);
193        }
194        catch (DirectoryException de)
195        {
196          lastException = de;
197        }
198      }
199      if (registered.isEmpty())
200      {
201        throw lastException;
202      }
203      remainingAttrs.removeAll(registered);
204    }
205  }
206
207  private void registerSchemaLdapSyntaxDefinitions(Set<Object> remainingAttrs) throws DirectoryException
208  {
209    for (final Object definition : remainingAttrs)
210    {
211      final ByteStringBuilder sb = new ByteStringBuilder();
212      sb.appendObject(definition);
213      if (definition.toString().contains(SchemaConstants.OID_OPENDS_SERVER_BASE))
214      {
215        try
216        {
217          final LDAPSyntaxDescription syntaxDesc = LDAPSyntaxDescriptionSyntax.decodeLDAPSyntax(
218              sb, DirectoryServer.getInstance().getServerContext(), schema, false, false);
219          schema.registerLdapSyntaxDescription(syntaxDesc, true);
220        }
221        catch (DirectoryException e)
222        {
223          // Filter error code to ignore exceptions raised on description syntaxes.
224          if (e.getResultCode() != ResultCode.UNWILLING_TO_PERFORM)
225          {
226            throw e;
227          }
228        }
229      }
230    }
231  }
232
233  /**
234   * Returns the schema that was read.
235   *
236   * @return the schema that was read.
237   */
238  @Override
239  public Schema getSchema()
240  {
241    return schema;
242  }
243}