001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.core.model.constraint;
028
029import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
030import gov.nist.secauto.metaschema.core.metapath.ISequence;
031import gov.nist.secauto.metaschema.core.metapath.MetapathException;
032import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter;
033import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
034import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.Level;
035import gov.nist.secauto.metaschema.core.util.ObjectUtils;
036
037import org.apache.logging.log4j.LogBuilder;
038import org.apache.logging.log4j.LogManager;
039import org.apache.logging.log4j.Logger;
040
041import java.util.Comparator;
042import java.util.List;
043import java.util.Objects;
044
045import edu.umd.cs.findbugs.annotations.NonNull;
046import edu.umd.cs.findbugs.annotations.Nullable;
047
048public class LoggingConstraintValidationHandler
049    extends AbstractConstraintValidationHandler {
050  private static final Logger LOGGER = LogManager.getLogger(DefaultConstraintValidator.class);
051  @NonNull
052  private IPathFormatter pathFormatter = IPathFormatter.METAPATH_PATH_FORMATER;
053
054  @Override
055  @NonNull
056  public IPathFormatter getPathFormatter() {
057    return pathFormatter;
058  }
059
060  public void setPathFormatter(@NonNull IPathFormatter pathFormatter) {
061    this.pathFormatter = Objects.requireNonNull(pathFormatter, "pathFormatter");
062  }
063
064  protected LogBuilder getLogBuilder(@NonNull Level level) {
065    LogBuilder retval;
066    switch (level) {
067    case CRITICAL:
068      retval = LOGGER.atFatal();
069      break;
070    case ERROR:
071      retval = LOGGER.atError();
072      break;
073    case WARNING:
074      retval = LOGGER.atWarn();
075      break;
076    case INFORMATIONAL:
077      retval = LOGGER.atInfo();
078      break;
079    default:
080      throw new UnsupportedOperationException(String.format("unsupported level '%s'", level));
081    }
082    return retval;
083  }
084
085  @Override
086  protected String toPath(@NonNull INodeItem nodeItem) {
087    return nodeItem.toPath(getPathFormatter());
088  }
089
090  protected boolean isLogged(@NonNull Level level) {
091    boolean retval;
092    switch (level) {
093    case CRITICAL:
094      retval = LOGGER.isFatalEnabled();
095      break;
096    case ERROR:
097      retval = LOGGER.isErrorEnabled();
098      break;
099    case WARNING:
100      retval = LOGGER.isWarnEnabled();
101      break;
102    case INFORMATIONAL:
103      retval = LOGGER.isInfoEnabled();
104      break;
105    default:
106      throw new UnsupportedOperationException(String.format("unsupported level '%s'", level));
107    }
108    return retval;
109  }
110
111  protected void logConstraint(
112      @NonNull Level level,
113      @NonNull INodeItem node,
114      @NonNull CharSequence message,
115      @Nullable Throwable cause) {
116    LogBuilder builder = getLogBuilder(level);
117    if (cause != null) {
118      builder.withThrowable(cause);
119    }
120
121    builder.log("{}: ({}) {}", level.name(), toPath(node), message);
122  }
123
124  @Override
125  public void handleCardinalityMinimumViolation(
126      @NonNull ICardinalityConstraint constraint,
127      @NonNull INodeItem node,
128      @NonNull ISequence<? extends INodeItem> targets) {
129    Level level = constraint.getLevel();
130    if (isLogged(level)) {
131      logConstraint(level, node, newCardinalityMinimumViolationMessage(constraint, node, targets), null);
132    }
133  }
134
135  @Override
136  public void handleCardinalityMaximumViolation(
137      @NonNull ICardinalityConstraint constraint,
138      @NonNull INodeItem node,
139      @NonNull ISequence<? extends INodeItem> targets) {
140    Level level = constraint.getLevel();
141    if (isLogged(level)) {
142      logConstraint(level, node, newCardinalityMaximumViolationMessage(constraint, node, targets), null);
143    }
144  }
145
146  @Override
147  public void handleIndexDuplicateKeyViolation(
148      @NonNull IIndexConstraint constraint,
149      @NonNull INodeItem node,
150      @NonNull INodeItem oldItem,
151      @NonNull INodeItem target) {
152    Level level = constraint.getLevel();
153    if (isLogged(level)) {
154      logConstraint(level, target, newIndexDuplicateKeyViolationMessage(constraint, node, oldItem, target), null);
155    }
156  }
157
158  @Override
159  public void handleUniqueKeyViolation(
160      @NonNull IUniqueConstraint constraint,
161      @NonNull INodeItem node,
162      @NonNull INodeItem oldItem,
163      @NonNull INodeItem target) {
164    Level level = constraint.getLevel();
165    if (isLogged(level)) {
166      logConstraint(level, target, newUniqueKeyViolationMessage(constraint, node, oldItem, target), null);
167    }
168  }
169
170  @SuppressWarnings("null")
171  @Override
172  public void handleKeyMatchError(
173      @NonNull IKeyConstraint constraint,
174      @NonNull INodeItem node,
175      @NonNull INodeItem target,
176      @NonNull MetapathException cause) {
177    Level level = constraint.getLevel();
178    if (isLogged(level)) {
179      logConstraint(level, target, cause.getLocalizedMessage(), cause);
180    }
181  }
182
183  @Override
184  public void handleMatchPatternViolation(
185      @NonNull IMatchesConstraint constraint,
186      @NonNull INodeItem node,
187      @NonNull INodeItem target,
188      @NonNull String value) {
189    Level level = constraint.getLevel();
190    if (isLogged(level)) {
191      logConstraint(level, target, newMatchPatternViolationMessage(constraint, node, target, value), null);
192    }
193  }
194
195  @Override
196  public void handleMatchDatatypeViolation(
197      @NonNull IMatchesConstraint constraint,
198      @NonNull INodeItem node,
199      @NonNull INodeItem target,
200      @NonNull String value,
201      @NonNull IllegalArgumentException cause) {
202    Level level = constraint.getLevel();
203    if (isLogged(level)) {
204      logConstraint(level, target, newMatchDatatypeViolationMessage(constraint, node, target, value), cause);
205    }
206  }
207
208  @Override
209  public void handleExpectViolation(
210      @NonNull IExpectConstraint constraint,
211      @NonNull INodeItem node,
212      @NonNull INodeItem target,
213      @NonNull DynamicContext dynamicContext) {
214    Level level = constraint.getLevel();
215    if (isLogged(level)) {
216      logConstraint(level, target, newExpectViolationMessage(constraint, node, target, dynamicContext), null);
217    }
218  }
219
220  @Override
221  public void handleAllowedValuesViolation(@NonNull List<IAllowedValuesConstraint> failedConstraints,
222      @NonNull INodeItem target) {
223
224    Level level = ObjectUtils.notNull(failedConstraints.stream()
225        .map(IConstraint::getLevel)
226        .max(Comparator.comparing(Level::ordinal))
227        .get());
228    if (isLogged(level)) {
229      logConstraint(level, target, newAllowedValuesViolationMessage(failedConstraints, target), null);
230    }
231  }
232
233  @Override
234  public void handleIndexDuplicateViolation(IIndexConstraint constraint, INodeItem node) {
235    Level level = Level.CRITICAL;
236    if (isLogged(level)) {
237      logConstraint(level, node, newIndexDuplicateViolationMessage(constraint, node), null);
238    }
239  }
240
241  @Override
242  public void handleIndexMiss(IIndexHasKeyConstraint constraint, INodeItem node, INodeItem target, List<String> key) {
243    Level level = constraint.getLevel();
244    if (isLogged(level)) {
245      logConstraint(level, node, newIndexMissMessage(constraint, node, target, key), null);
246    }
247  }
248
249}