001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2017 ForgeRock AS.
016 */
017package org.opends.server.tasks;
018
019import java.io.File;
020import java.util.LinkedList;
021import java.util.List;
022import java.util.TreeSet;
023
024import org.forgerock.i18n.LocalizableMessage;
025import org.forgerock.i18n.slf4j.LocalizedLogger;
026import org.forgerock.opendj.config.server.ConfigException;
027import org.forgerock.opendj.ldap.ByteString;
028import org.forgerock.opendj.ldap.ResultCode;
029import org.forgerock.opendj.ldap.schema.AttributeType;
030import org.forgerock.opendj.server.config.server.SynchronizationProviderCfg;
031import org.opends.server.api.ClientConnection;
032import org.opends.server.api.SynchronizationProvider;
033import org.opends.server.backends.task.Task;
034import org.opends.server.backends.task.TaskState;
035import org.opends.server.core.DirectoryServer;
036import org.opends.server.core.SchemaConfigManager;
037import org.opends.server.types.Attribute;
038import org.opends.server.types.AttributeBuilder;
039import org.opends.server.types.DirectoryException;
040import org.opends.server.types.Entry;
041import org.opends.server.types.InitializationException;
042import org.opends.server.types.LockManager.DNLock;
043import org.opends.server.types.Modification;
044import org.opends.server.types.Operation;
045import org.opends.server.types.Privilege;
046import org.opends.server.types.Schema;
047
048import static org.opends.messages.TaskMessages.*;
049import static org.opends.server.config.ConfigConstants.*;
050import static org.opends.server.core.DirectoryServer.*;
051import static org.opends.server.util.StaticUtils.*;
052
053/**
054 * This class provides an implementation of a Directory Server task that can be
055 * used to add the contents of a new schema file into the server schema.
056 */
057public class AddSchemaFileTask
058       extends Task
059{
060  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
061
062  /** The list of files to be added to the server schema. */
063  private TreeSet<String> filesToAdd;
064
065  @Override
066  public LocalizableMessage getDisplayName() {
067    return INFO_TASK_ADD_SCHEMA_FILE_NAME.get();
068  }
069
070  @Override
071  public void initializeTask()
072         throws DirectoryException
073  {
074    // If the client connection is available, then make sure the associated
075    // client has the UPDATE_SCHEMA privilege.
076    Operation operation = getOperation();
077    if (operation != null)
078    {
079      ClientConnection clientConnection = operation.getClientConnection();
080      if (! clientConnection.hasPrivilege(Privilege.UPDATE_SCHEMA, operation))
081      {
082        LocalizableMessage message = ERR_TASK_ADDSCHEMAFILE_INSUFFICIENT_PRIVILEGES.get();
083        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
084                                     message);
085      }
086    }
087
088    // Get the attribute that specifies which schema file(s) to add.
089    Entry taskEntry = getTaskEntry();
090    AttributeType attrType = DirectoryServer.getSchema().getAttributeType(ATTR_TASK_ADDSCHEMAFILE_FILENAME);
091    List<Attribute> attrList = taskEntry.getAttribute(attrType);
092    if (attrList.isEmpty())
093    {
094      LocalizableMessage message = ERR_TASK_ADDSCHEMAFILE_NO_FILENAME.get(
095          ATTR_TASK_ADDSCHEMAFILE_FILENAME, taskEntry.getName());
096      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
097    }
098
099    // Get the name(s) of the schema files to add and make sure they exist in
100    // the schema directory.
101    String schemaInstanceDirectory =
102      SchemaConfigManager.getSchemaDirectoryPath();
103    filesToAdd = new TreeSet<>();
104    for (Attribute a : attrList)
105    {
106      for (ByteString v  : a)
107      {
108        String filename = v.toString();
109        filesToAdd.add(filename);
110
111        try
112        {
113          File schemaFile = new File(schemaInstanceDirectory, filename);
114          if (! schemaFile.exists())
115          {
116            LocalizableMessage message = ERR_TASK_ADDSCHEMAFILE_NO_SUCH_FILE.get(
117                filename, schemaInstanceDirectory);
118            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
119                                         message);
120          }
121        }
122        catch (Exception e)
123        {
124          logger.traceException(e);
125
126          LocalizableMessage message = ERR_TASK_ADDSCHEMAFILE_ERROR_CHECKING_FOR_FILE.get(
127              filename, schemaInstanceDirectory,
128              getExceptionMessage(e));
129          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
130                                       message, e);
131        }
132      }
133    }
134
135    // Create a new dummy schema and make sure that we can add the contents of
136    // all the schema files into it.  Even though this duplicates work we'll
137    // have to do later, it will be good to do it now as well so we can reject
138    // the entry immediately which will fail the attempt by the client to add it
139    // to the server, rather than having to check its status after the fact.
140    Schema schema = DirectoryServer.getSchema().duplicate();
141    for (String schemaFile : filesToAdd)
142    {
143      try
144      {
145        SchemaConfigManager.loadSchemaFile(schema, null, schemaFile);
146      }
147      catch (ConfigException | InitializationException e)
148      {
149        logger.traceException(e);
150
151        LocalizableMessage message = ERR_TASK_ADDSCHEMAFILE_ERROR_LOADING_SCHEMA_FILE.get(schemaFile, e.getMessage());
152        throw new DirectoryException(getServerErrorResultCode(), message, e);
153      }
154    }
155  }
156
157  @Override
158  protected TaskState runTask()
159  {
160    // Obtain a write lock on the server schema so that we can be sure nothing
161    // else tries to write to it at the same time.
162    final DNLock schemaLock = DirectoryServer.getLockManager().tryWriteLockEntry(getSchemaDN());
163    if (schemaLock == null)
164    {
165      logger.error(ERR_TASK_ADDSCHEMAFILE_CANNOT_LOCK_SCHEMA, getSchemaDN());
166      return TaskState.STOPPED_BY_ERROR;
167    }
168
169    try
170    {
171      LinkedList<Modification> mods = new LinkedList<>();
172      Schema schema = DirectoryServer.getSchema().duplicate();
173      for (String schemaFile : filesToAdd)
174      {
175        try
176        {
177          List<Modification> modList = SchemaConfigManager.loadSchemaFileReturnModifications(schema, schemaFile, null);
178          for (Modification m : modList)
179          {
180            Attribute a = m.getAttribute();
181            AttributeBuilder builder = new AttributeBuilder(a.getAttributeDescription());
182            for (ByteString v : a)
183            {
184              builder.add(Schema.addSchemaFileToElementDefinitionIfAbsent(v.toString(), schemaFile));
185            }
186
187            mods.add(new Modification(m.getModificationType(), builder.toAttribute()));
188          }
189        }
190        catch (ConfigException | InitializationException e)
191        {
192          logger.traceException(e);
193          logger.error(ERR_TASK_ADDSCHEMAFILE_ERROR_LOADING_SCHEMA_FILE, schemaFile, e.getMessage());
194          return TaskState.STOPPED_BY_ERROR;
195        }
196      }
197
198      if (! mods.isEmpty())
199      {
200        for (SynchronizationProvider<SynchronizationProviderCfg> provider :
201             DirectoryServer.getSynchronizationProviders())
202        {
203          try
204          {
205            provider.processSchemaChange(mods);
206          }
207          catch (Exception e)
208          {
209            logger.traceException(e);
210
211            logger.error(ERR_TASK_ADDSCHEMAFILE_CANNOT_NOTIFY_SYNC_PROVIDER,
212                provider.getClass().getName(), getExceptionMessage(e));
213          }
214        }
215
216        Schema.writeConcatenatedSchema();
217      }
218
219      schema.setYoungestModificationTime(System.currentTimeMillis());
220      DirectoryServer.setSchema(schema);
221      return TaskState.COMPLETED_SUCCESSFULLY;
222    }
223    finally
224    {
225      schemaLock.unlock();
226    }
227  }
228}