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-2016 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.opends.server.admin.std.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.*;
038import org.opends.server.types.LockManager.DNLock;
039
040import static org.opends.messages.TaskMessages.*;
041import static org.opends.server.config.ConfigConstants.*;
042import static org.opends.server.core.DirectoryServer.*;
043import static org.opends.server.util.ServerConstants.*;
044import static org.opends.server.util.StaticUtils.*;
045
046/**
047 * This class provides an implementation of a Directory Server task that can be
048 * used to add the contents of a new schema file into the server schema.
049 */
050public class AddSchemaFileTask
051       extends Task
052{
053  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
054
055  /** The list of files to be added to the server schema. */
056  private TreeSet<String> filesToAdd;
057
058  /** {@inheritDoc} */
059  @Override
060  public LocalizableMessage getDisplayName() {
061    return INFO_TASK_ADD_SCHEMA_FILE_NAME.get();
062  }
063
064  /** {@inheritDoc} */
065  @Override
066  public void initializeTask()
067         throws DirectoryException
068  {
069    // If the client connection is available, then make sure the associated
070    // client has the UPDATE_SCHEMA privilege.
071    Operation operation = getOperation();
072    if (operation != null)
073    {
074      ClientConnection clientConnection = operation.getClientConnection();
075      if (! clientConnection.hasPrivilege(Privilege.UPDATE_SCHEMA, operation))
076      {
077        LocalizableMessage message = ERR_TASK_ADDSCHEMAFILE_INSUFFICIENT_PRIVILEGES.get();
078        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
079                                     message);
080      }
081    }
082
083
084    // Get the attribute that specifies which schema file(s) to add.
085    Entry taskEntry = getTaskEntry();
086    AttributeType attrType = DirectoryServer.getAttributeType(ATTR_TASK_ADDSCHEMAFILE_FILENAME);
087    List<Attribute> attrList = taskEntry.getAttribute(attrType);
088    if (attrList.isEmpty())
089    {
090      LocalizableMessage message = ERR_TASK_ADDSCHEMAFILE_NO_FILENAME.get(
091          ATTR_TASK_ADDSCHEMAFILE_FILENAME, taskEntry.getName());
092      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
093    }
094
095
096    // Get the name(s) of the schema files to add and make sure they exist in
097    // the schema directory.
098    String schemaInstanceDirectory =
099      SchemaConfigManager.getSchemaDirectoryPath();
100    filesToAdd = new TreeSet<>();
101    for (Attribute a : attrList)
102    {
103      for (ByteString v  : a)
104      {
105        String filename = v.toString();
106        filesToAdd.add(filename);
107
108        try
109        {
110          File schemaFile = new File(schemaInstanceDirectory, filename);
111          if (! schemaFile.exists())
112          {
113            LocalizableMessage message = ERR_TASK_ADDSCHEMAFILE_NO_SUCH_FILE.get(
114                filename, schemaInstanceDirectory);
115            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
116                                         message);
117          }
118        }
119        catch (Exception e)
120        {
121          logger.traceException(e);
122
123          LocalizableMessage message = ERR_TASK_ADDSCHEMAFILE_ERROR_CHECKING_FOR_FILE.get(
124              filename, schemaInstanceDirectory,
125              getExceptionMessage(e));
126          throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
127                                       message, e);
128        }
129      }
130    }
131
132
133    // Create a new dummy schema and make sure that we can add the contents of
134    // all the schema files into it.  Even though this duplicates work we'll
135    // have to do later, it will be good to do it now as well so we can reject
136    // the entry immediately which will fail the attempt by the client to add it
137    // to the server, rather than having to check its status after the fact.
138    Schema schema = DirectoryServer.getSchema().duplicate();
139    for (String schemaFile : filesToAdd)
140    {
141      try
142      {
143        SchemaConfigManager.loadSchemaFile(schema, schemaFile);
144      }
145      catch (ConfigException | InitializationException e)
146      {
147        logger.traceException(e);
148
149        LocalizableMessage message = ERR_TASK_ADDSCHEMAFILE_ERROR_LOADING_SCHEMA_FILE.get(schemaFile, e.getMessage());
150        throw new DirectoryException(getServerErrorResultCode(), message, e);
151      }
152    }
153  }
154
155
156
157  /** {@inheritDoc} */
158  @Override
159  protected TaskState runTask()
160  {
161    // Obtain a write lock on the server schema so that we can be sure nothing
162    // else tries to write to it at the same time.
163    final DNLock schemaLock = DirectoryServer.getLockManager().tryWriteLockEntry(getSchemaDN());
164    if (schemaLock == null)
165    {
166      logger.error(ERR_TASK_ADDSCHEMAFILE_CANNOT_LOCK_SCHEMA, getSchemaDN());
167      return TaskState.STOPPED_BY_ERROR;
168    }
169
170    try
171    {
172      LinkedList<Modification> mods = new LinkedList<>();
173      Schema schema = DirectoryServer.getSchema().duplicate();
174      for (String schemaFile : filesToAdd)
175      {
176        try
177        {
178          List<Modification> modList = SchemaConfigManager.loadSchemaFile(schema, schemaFile);
179          for (Modification m : modList)
180          {
181            Attribute a = m.getAttribute();
182            AttributeType attrType = a.getAttributeDescription().getAttributeType();
183            AttributeBuilder builder = new AttributeBuilder(attrType, attrType.getNameOrOID());
184            for (ByteString v : a)
185            {
186              String s = v.toString();
187              if (!s.contains(SCHEMA_PROPERTY_FILENAME))
188              {
189                if (s.endsWith(" )"))
190                {
191                 s = s.substring(0, s.length()-1) + SCHEMA_PROPERTY_FILENAME +
192                     " '" + schemaFile + "' )";
193                }
194                else if (s.endsWith(")"))
195                {
196                 s = s.substring(0, s.length()-1) + " " +
197                     SCHEMA_PROPERTY_FILENAME + " '" + schemaFile + "' )";
198                }
199              }
200
201              builder.add(s);
202            }
203
204            mods.add(new Modification(m.getModificationType(), builder
205                .toAttribute()));
206          }
207        }
208        catch (ConfigException | InitializationException e)
209        {
210          logger.traceException(e);
211          logger.error(ERR_TASK_ADDSCHEMAFILE_ERROR_LOADING_SCHEMA_FILE, schemaFile, e.getMessage());
212          return TaskState.STOPPED_BY_ERROR;
213        }
214      }
215
216      if (! mods.isEmpty())
217      {
218        for (SynchronizationProvider<SynchronizationProviderCfg> provider :
219             DirectoryServer.getSynchronizationProviders())
220        {
221          try
222          {
223            provider.processSchemaChange(mods);
224          }
225          catch (Exception e)
226          {
227            logger.traceException(e);
228
229            logger.error(ERR_TASK_ADDSCHEMAFILE_CANNOT_NOTIFY_SYNC_PROVIDER,
230                provider.getClass().getName(), getExceptionMessage(e));
231          }
232        }
233
234        Schema.writeConcatenatedSchema();
235      }
236
237      schema.setYoungestModificationTime(System.currentTimeMillis());
238      DirectoryServer.setSchema(schema);
239      return TaskState.COMPLETED_SUCCESSFULLY;
240    }
241    finally
242    {
243      schemaLock.unlock();
244    }
245  }
246}
247