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 2006-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.extensions;
018
019import static org.opends.server.util.StaticUtils.*;
020
021import java.util.HashSet;
022import java.util.List;
023import java.util.Set;
024import java.util.SortedSet;
025
026import org.forgerock.i18n.LocalizableMessage;
027import org.forgerock.i18n.LocalizableMessageDescriptor;
028import org.forgerock.opendj.ldap.DN;
029import org.opends.server.types.DirectoryException;
030import org.forgerock.i18n.slf4j.LocalizedLogger;
031import org.forgerock.opendj.ldap.ResultCode;
032import org.opends.server.api.MonitorData;
033import org.opends.server.types.SearchFilter;
034
035/** This class provides some common tools to all entry cache implementations. */
036public class EntryCacheCommon
037{
038
039  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
040
041  /**
042   * Configuration phases. Each value identifies a configuration step:
043   * - PHASE_INIT       when invoking method initializeEntryCache()
044   * - PHASE_ACCEPTABLE when invoking method isConfigurationChangeAcceptable()
045   * - PHASE_APPLY      when invoking method applyConfigurationChange()
046   */
047  public static enum ConfigPhase
048  {
049    /** Indicates that entry cache is in initialization check phase. */
050    PHASE_INIT,
051    /** Indicates that entry cache is in configuration check phase. */
052    PHASE_ACCEPTABLE,
053    /** Indicates that entry cache is applying its configuration. */
054    PHASE_APPLY
055  }
056
057  /**
058   * Error handler used by local methods to report configuration error.
059   * The error handler simplifies the code of initializeEntryCache(),
060   * isConfigurationChangeAcceptable() and applyConfigurationChanges() methods.
061   */
062  public class ConfigErrorHandler
063  {
064    /** Configuration phase. */
065    private EntryCacheCommon.ConfigPhase _configPhase;
066
067    /** Unacceptable reasons. Used when _configPhase is PHASE_ACCEPTABLE. */
068    private List<LocalizableMessage> _unacceptableReasons;
069
070    /** Error messages. Used when _configPhase is PHASE_APPLY. */
071    private List<LocalizableMessage> _errorMessages;
072
073    /** Result code. Used when _configPhase is PHASE_APPLY. */
074    private ResultCode _resultCode;
075
076    /**
077     * Acceptable Configuration ? Used when _configPhase is PHASE_ACCEPTABLE
078     * or PHASE_APPLY.
079     */
080    private boolean _isAcceptable;
081
082    /**
083     * Indicates whether administrative action is required or not. Used when
084     * _configPhase is PHASE_APPLY.
085     */
086    private boolean _isAdminActionRequired;
087
088    /**
089     * Create an error handler.
090     *
091     * @param configPhase          the configuration phase for which the
092     *                             error handler is used
093     * @param unacceptableReasons  the reasons why the configuration cannot
094     *                             be applied (during PHASE_ACCEPTABLE phase)
095     * @param errorMessages        the errors found when applying a new
096     *                             configuration (during PHASE_APPLY phase)
097     */
098    public ConfigErrorHandler (
099        EntryCacheCommon.ConfigPhase configPhase,
100        List<LocalizableMessage> unacceptableReasons,
101        List<LocalizableMessage> errorMessages
102        )
103    {
104      _configPhase           = configPhase;
105      _unacceptableReasons   = unacceptableReasons;
106      _errorMessages         = errorMessages;
107      _resultCode            = ResultCode.SUCCESS;
108      _isAcceptable          = true;
109      _isAdminActionRequired = false;
110    }
111
112    /**
113     * Report an error.
114     *
115     * @param error        the error to report
116     * @param isAcceptable <code>true</code> if the configuration is acceptable
117     * @param resultCode   the change result for the current configuration
118     */
119    public void reportError(
120            LocalizableMessage error,
121            boolean isAcceptable,
122            ResultCode resultCode
123    )
124    {
125      switch (_configPhase)
126      {
127      case PHASE_INIT:
128        {
129        _errorMessages.add (error);
130        _isAcceptable = isAcceptable;
131        break;
132        }
133      case PHASE_ACCEPTABLE:
134        {
135        _unacceptableReasons.add (error);
136        _isAcceptable = isAcceptable;
137        break;
138        }
139      case PHASE_APPLY:
140        {
141        _errorMessages.add (error);
142        _isAcceptable = isAcceptable;
143        if (_resultCode == ResultCode.SUCCESS)
144        {
145          _resultCode = resultCode;
146        }
147        break;
148        }
149      }
150    }
151
152    /**
153     * Report an error.
154     *
155     * @param error        the error to report
156     * @param isAcceptable <code>true</code> if the configuration is acceptable
157     * @param resultCode   the change result for the current configuration
158     * @param isAdminActionRequired <code>true</code> if administrative action
159     *                              is required or <code>false</code> otherwise
160     */
161    public void reportError(
162            LocalizableMessage error,
163            boolean isAcceptable,
164            ResultCode resultCode,
165            boolean isAdminActionRequired
166    )
167    {
168      switch (_configPhase)
169      {
170      case PHASE_INIT:
171        {
172        logger.error(error);
173        break;
174        }
175      case PHASE_ACCEPTABLE:
176        {
177        _unacceptableReasons.add (error);
178        _isAcceptable = isAcceptable;
179        break;
180        }
181      case PHASE_APPLY:
182        {
183        _errorMessages.add (error);
184        _isAcceptable = isAcceptable;
185        if (_resultCode == ResultCode.SUCCESS)
186        {
187          _resultCode = resultCode;
188        }
189        _isAdminActionRequired = isAdminActionRequired;
190        break;
191        }
192      }
193    }
194
195    /**
196     * Get the current result code that was elaborated right after a
197     * configuration has been applied.
198     *
199     * @return the current result code
200     */
201    public ResultCode getResultCode()
202    {
203      return _resultCode;
204    }
205
206    /**
207     * Get the current isAcceptable flag. The isAcceptable flag is elaborated
208     * right after the configuration was checked.
209     *
210     * @return the isAcceptable flag
211     */
212    public boolean getIsAcceptable()
213    {
214      return _isAcceptable;
215    }
216
217    /**
218     * Get the current unacceptable reasons. The unacceptable reasons are
219     * elaborated when the configuration is checked.
220     *
221     * @return the list of unacceptable reasons
222     */
223    public List<LocalizableMessage> getUnacceptableReasons()
224    {
225      return _unacceptableReasons;
226    }
227
228    /**
229     * Get the current error messages. The error messages are elaborated
230     * when the configuration is applied.
231     *
232     * @return the list of error messages
233     */
234    public List<LocalizableMessage> getErrorMessages()
235    {
236      return _errorMessages;
237    }
238
239    /**
240     * Get the current configuration phase. The configuration phase indicates
241     * whether the entry cache is in initialization step, or in configuration
242     * checking step or in configuration being applied step.
243     *
244     * @return the current configuration phase.
245     */
246    public ConfigPhase getConfigPhase()
247    {
248      return _configPhase;
249    }
250
251    /**
252     * Get the current isAdminActionRequired flag as determined after apply
253     * action has been taken on a given configuration.
254     *
255     * @return the isAdminActionRequired flag
256     */
257    public boolean getIsAdminActionRequired()
258    {
259      return _isAdminActionRequired;
260    }
261  } // ConfigErrorHandler
262
263
264  /**
265   * Reads a list of string filters and convert it to a list of search
266   * filters.
267   *
268   * @param filters  the list of string filter to convert to search filters
269   * @param decodeErrorMsg  the error message ID to use in case of error
270   * @param errorHandler  error handler to report filter decoding errors on
271   * @param configEntryDN  the entry cache configuration DN
272   *
273   * @return the set of search filters
274   */
275  public static Set<SearchFilter> getFilters(SortedSet<String> filters,
276      LocalizableMessageDescriptor.Arg3<Object, Object, Object> decodeErrorMsg,
277      ConfigErrorHandler errorHandler, DN configEntryDN)
278  {
279    // Returned value
280    Set<SearchFilter> searchFilters = new HashSet<>();
281
282    // Convert the string filters to search filters.
283    if (filters != null && ! filters.isEmpty())
284    {
285      for (String curFilter: filters)
286      {
287        try
288        {
289          searchFilters.add (SearchFilter.createFilterFromString (curFilter));
290        }
291        catch (DirectoryException de)
292        {
293          // We couldn't decode this filter. Report an error and continue.
294          LocalizableMessage message = decodeErrorMsg.get(String.valueOf(configEntryDN),
295            curFilter, de.getMessage() != null ? de.getMessage() :
296              stackTraceToSingleLineString(de));
297          errorHandler.reportError(message, false,
298            ResultCode.INVALID_ATTRIBUTE_SYNTAX);
299        }
300      }
301    }
302
303    // done
304    return searchFilters;
305  }
306
307
308  /**
309   * Create a new error handler.
310   *
311   * @param configPhase          the configuration phase for which the
312   *                             error handler is used
313   * @param unacceptableReasons  the reasons why the configuration cannot
314   *                             be applied (during PHASE_ACCEPTABLE phase)
315   * @param errorMessages        the errors found when applying a new
316   *                             configuration (during PHASE_APPLY phase)
317   *
318   * @return a new configuration error handler
319   */
320  public static ConfigErrorHandler getConfigErrorHandler (
321      EntryCacheCommon.ConfigPhase  configPhase,
322      List<LocalizableMessage> unacceptableReasons,
323      List<LocalizableMessage> errorMessages
324      )
325  {
326    EntryCacheCommon ec = new EntryCacheCommon();
327    return ec.new ConfigErrorHandler(
328        configPhase, unacceptableReasons, errorMessages);
329  }
330
331
332  /**
333   * Constructs a set of generic attributes containing entry cache
334   * monitor data. Note that <code>null</code> can be passed in
335   * place of any argument to denote the argument is omitted, such
336   * is when no state data of a given kind is available or can be
337   * provided.
338   *
339   * @param cacheHits      number of cache hits.
340   * @param cacheMisses    number of cache misses.
341   * @param cacheSize      size of the current cache, in bytes.
342   * @param maxCacheSize   maximum allowed cache size, in bytes.
343   * @param cacheCount     number of entries stored in the cache.
344   * @param maxCacheCount  maximum number of cache entries allowed.
345   *
346   * @return  A set of generic attributes containing monitor data.
347   */
348  public static MonitorData getGenericMonitorData(
349    Long cacheHits,
350    Long cacheMisses,
351    Long cacheSize,
352    Long maxCacheSize,
353    Long cacheCount,
354    Long maxCacheCount)
355  {
356    MonitorData attrs = new MonitorData();
357
358    if (cacheHits != null)
359    {
360      attrs.add("entryCacheHits", cacheHits);
361
362      // Cache misses is required to get cache tries and hit ratio.
363      if (cacheMisses != null)
364      {
365        Long cacheTries = cacheHits + cacheMisses;
366        attrs.add("entryCacheTries", cacheTries);
367
368        Double hitRatioRaw = cacheTries > 0 ? cacheHits.doubleValue()
369            / cacheTries.doubleValue() : cacheHits.doubleValue() / 1;
370        Double hitRatio = hitRatioRaw * 100D;
371        attrs.add("entryCacheHitRatio", hitRatio);
372      }
373    }
374
375    attrs.addIfNotNull("currentEntryCacheSize", cacheSize);
376    attrs.addIfNotNull("maxEntryCacheSize", maxCacheSize);
377    attrs.addIfNotNull("currentEntryCacheCount", cacheCount);
378    attrs.addIfNotNull("maxEntryCacheCount", maxCacheCount);
379
380    return attrs;
381  }
382}