001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3.exception;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Set;
023import java.util.stream.Collectors;
024import java.util.stream.Stream;
025
026import org.apache.commons.lang3.StringUtils;
027import org.apache.commons.lang3.tuple.ImmutablePair;
028import org.apache.commons.lang3.tuple.Pair;
029
030/**
031 * Default implementation of the context storing the label-value pairs for contexted exceptions.
032 * <p>
033 * This implementation is serializable, however this is dependent on the values that
034 * are added also being serializable.
035 * </p>
036 *
037 * @see ContextedException
038 * @see ContextedRuntimeException
039 * @since 3.0
040 */
041public class DefaultExceptionContext implements ExceptionContext, Serializable {
042
043    /** The serialization version. */
044    private static final long serialVersionUID = 20110706L;
045
046    /** The list storing the label-data pairs. */
047    private final List<Pair<String, Object>> contextValues = new ArrayList<>();
048
049    /**
050     * {@inheritDoc}
051     */
052    @Override
053    public DefaultExceptionContext addContextValue(final String label, final Object value) {
054        contextValues.add(new ImmutablePair<>(label, value));
055        return this;
056    }
057
058    /**
059     * {@inheritDoc}
060     */
061    @Override
062    public List<Pair<String, Object>> getContextEntries() {
063        return contextValues;
064    }
065
066    /**
067     * {@inheritDoc}
068     */
069    @Override
070    public Set<String> getContextLabels() {
071        return stream().map(Pair::getKey).collect(Collectors.toSet());
072    }
073
074    /**
075     * {@inheritDoc}
076     */
077    @Override
078    public List<Object> getContextValues(final String label) {
079        return stream().filter(pair -> StringUtils.equals(label, pair.getKey())).map(Pair::getValue).collect(Collectors.toList());
080    }
081
082    /**
083     * {@inheritDoc}
084     */
085    @Override
086    public Object getFirstContextValue(final String label) {
087        return stream().filter(pair -> StringUtils.equals(label, pair.getKey())).findFirst().map(Pair::getValue).orElse(null);
088    }
089
090    /**
091     * Builds the message containing the contextual information.
092     *
093     * @param baseMessage  the base exception message <b>without</b> context information appended
094     * @return the exception message <b>with</b> context information appended, never null
095     */
096    @Override
097    public String getFormattedExceptionMessage(final String baseMessage) {
098        final StringBuilder buffer = new StringBuilder(256);
099        if (baseMessage != null) {
100            buffer.append(baseMessage);
101        }
102
103        if (!contextValues.isEmpty()) {
104            if (buffer.length() > 0) {
105                buffer.append('\n');
106            }
107            buffer.append("Exception Context:\n");
108
109            int i = 0;
110            for (final Pair<String, Object> pair : contextValues) {
111                buffer.append("\t[");
112                buffer.append(++i);
113                buffer.append(':');
114                buffer.append(pair.getKey());
115                buffer.append("=");
116                final Object value = pair.getValue();
117                if (value == null) {
118                    buffer.append("null");
119                } else {
120                    String valueStr;
121                    try {
122                        valueStr = value.toString();
123                    } catch (final Exception e) {
124                        valueStr = "Exception thrown on toString(): " + ExceptionUtils.getStackTrace(e);
125                    }
126                    buffer.append(valueStr);
127                }
128                buffer.append("]\n");
129            }
130            buffer.append("---------------------------------");
131        }
132        return buffer.toString();
133    }
134
135    /**
136     * {@inheritDoc}
137     */
138    @Override
139    public DefaultExceptionContext setContextValue(final String label, final Object value) {
140        contextValues.removeIf(p -> StringUtils.equals(label, p.getKey()));
141        addContextValue(label, value);
142        return this;
143    }
144
145    private Stream<Pair<String, Object>> stream() {
146        return contextValues.stream();
147    }
148
149}