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