public class Appender extends AppenderBase<ILoggingEvent> {

    private final Configuration configuration;
    private final Converter converter;
    private final Sender sender;

    /**
     * Constructors a new appender.
     */
    public Appender() {
        this(new Configuration(), new Sender());
    }

    Appender(final Configuration configuration, final Sender sender) {
        this.configuration = configuration;
        this.converter = new Converter(configuration);
        this.sender = sender;
    }

    @Override
    public void start() {
        if (configuration.isInvalid()) {
            configuration.addErrors(this);
            return;
        }

        sender.start(configuration, this);
        super.start();
    }

    @Override
    public void stop() {

        if (!isStarted()) {
            return;
        }

        sender.stop();
        super.stop();
    }

    @Override
    protected void append(final ILoggingEvent event) {

        if (!isStarted() || configuration.isStageIgnored() || shouldNotNotifyFor(event)) {
            return;
        }

        final NotificationVO notification = converter.convertToNotification(event);
        sender.send(notification);
    }

    /**
     * Sets the api key used for authentication and authorization with the bugsnag api.
     *
     * @param apiKey the api key
     */
    public void setApiKey(final String apiKey) {
        this.configuration.setApiKey(apiKey);
    }

    /**
     * Allows switching between http and https for communication with the bugsnag api.
     *
     * <p/>Optional, default: false.
     *
     * @param sslEnabled true for https, false for http
     */
    public void setSslEnabled(final boolean sslEnabled) {
        this.configuration.setSslEnabled(sslEnabled);
    }

    /**
     * Sets the bugsnag api endpoint's host name.
     *
     * <p/>Optional, default: notify.bugsnag.com
     *
     * @param endpoint the host name
     */
    public void setEndpoint(final String endpoint) {
        this.configuration.setEndpoint(endpoint);
    }

    /**
     * Sets the release stage used to check whether notification is enabled on this stage.
     *
     * <p/>Optional, default: production
     *
     * @param releaseStage the release stage
     */
    public void setReleaseStage(final String releaseStage) {
        this.configuration.setReleaseStage(releaseStage);
    }

    /**
     * Sets the notifiable release stages.
     *
     * <p/>Optional, default: production
     *
     * @param notifyReleaseStages a comma separated set of release stages
     */
    public void setNotifyReleaseStages(final String notifyReleaseStages) {
        this.configuration.setNotifyReleaseStages(splitByCommaToSet(notifyReleaseStages));
    }

    /**
     * Sets filters used to filter out unwanted key value pairs from data provided by the
     * specified {@link MetaDataProvider} implementation.
     *
     * <p/>Optional, default: empty
     *
     * @param filters a comma separated set of filter words
     */
    public void setFilters(final String filters) {
        this.configuration.setFilters(splitByCommaToSet(filters));
    }

    /**
     * Sets project packages used to highlight project specific lines in the exception's
     * stack trace.
     *
     * <p/>Optional, default: empty
     *
     * @param projectPackages a comma separated set of package names
     */
    public void setProjectPackages(final String projectPackages) {
        this.configuration.setProjectPackages(splitByCommaToSet(projectPackages));
    }

    /**
     * Sets ignored classes used to filter throwables which should not be send to the api.
     *
     * <p/>Optional, default: empty
     *
     * @param ignoreClasses a comma separated set of fully qualified class names
     */
    public void setIgnoreClasses(final String ignoreClasses) {
        this.configuration.setIgnoreClasses(splitByCommaToSet(ignoreClasses));
    }

    /**
     * Sets the {@link MetaDataProvider} implementation.
     *
     * <p/>Optional
     *
     * @param metaDataProviderClassName the fully qualified class name
     */
    public void setMetaDataProvider(final String metaDataProviderClassName) {
        this.configuration.setMetaDataProviderClassName(metaDataProviderClassName);
    }

    private Set<String> splitByCommaToSet(final String input) {
        return Sets.newHashSet(Splitter.on(",").trimResults().split(input));
    }

    private boolean shouldNotNotifyFor(final ILoggingEvent event) {
        return !containsException(event) || !configuration.shouldNotifyFor(event.getThrowableProxy().getClassName());
    }

    private boolean containsException(final ILoggingEvent event) {
        return event.getThrowableProxy() != null;
    }
} public class Configuration implements JsonFilterProvider {

    private static final String DEFAULT_ENDPOINT = "notify.bugsnag.com";
    private static final boolean DEFAULT_USE_SSL = false;
    private static final String DEFAULT_RELEASE_STAGE = "production";
    private static final String PROTOCOL_HOST_SEPARATOR = "://";
    private static final String HTTPS = "https";
    private static final String HTTP = "http";

    private String endpoint = DEFAULT_ENDPOINT;
    private String apiKey;
    private String releaseStage = DEFAULT_RELEASE_STAGE;
    private boolean sslEnabled = DEFAULT_USE_SSL;
    private Set<String> notifyReleaseStages = Sets.newHashSet(DEFAULT_RELEASE_STAGE);
    private Set<String> filters = Sets.newHashSet();
    private Set<String> projectPackages = Sets.newHashSet();
    private Set<String> ignoreClasses = Sets.newHashSet();
    private Optional<String> metaDataProviderClassName = Optional.absent();

    /**
     * @return the full url of the endpoint
     */
    public String getEndpointWithProtocol() {

        final String protocol;
        if (sslEnabled) {
            protocol = HTTPS;
        } else {
            protocol = HTTP;
        }

        return protocol + PROTOCOL_HOST_SEPARATOR + endpoint;
    }

    /**
     * @see Appender#setEndpoint(String)
     * @param endpoint the host name of the endpoint
     */
    public void setEndpoint(final String endpoint) {
        this.endpoint = endpoint;
    }

    /**
     * @see Appender#setApiKey(String)
     * @return the api key
     */
    public String getApiKey() {
        return apiKey;
    }

    /**
     * @see Appender#setApiKey(String)
     * @param apiKey the api key
     */
    public void setApiKey(final String apiKey) {
        this.apiKey = apiKey;
    }

    /**
     * @see Appender#setReleaseStage(String)
     * @return the release stage
     */
    public String getReleaseStage() {
        return releaseStage;
    }

    /**
     * @see Appender#setReleaseStage(String)
     * @param releaseStage the release stage
     */
    public void setReleaseStage(final String releaseStage) {
        this.releaseStage = releaseStage;
    }

    /**
     * @see Appender#setSslEnabled(boolean)
     * @return true when https is used, false when http is used
     */
    public boolean isSslEnabled() {
        return sslEnabled;
    }

    /**
     * @see Appender#setSslEnabled(boolean)
     * @param sslEnabled true to enabled https, false for http
     */
    public void setSslEnabled(final boolean sslEnabled) {
        this.sslEnabled = sslEnabled;
    }

    /**
     * @see Appender#setNotifyReleaseStages(String)
     * @param notifyReleaseStages the notifiable release stages
     */
    public void setNotifyReleaseStages(final Set<String> notifyReleaseStages) {
        this.notifyReleaseStages = notifyReleaseStages;
    }

    /**
     * @see Appender#setFilters(String)
     * @param filters the meta data filters
     */
    public void setFilters(final Set<String> filters) {
        this.filters = filters;
    }

    /**
     * @see Appender#setProjectPackages(String)
     * @param projectPackages the project packages
     */
    public void setProjectPackages(final Set<String> projectPackages) {
        this.projectPackages = projectPackages;
    }

    /**
     * @see Appender#setIgnoreClasses(String)
     * @param ignoreClasses the ignored throwable classes
     */
    public void setIgnoreClasses(final Set<String> ignoreClasses) {
        this.ignoreClasses = ignoreClasses;
    }

    /**
     * @return true when a {@link MetaDataProvider} class name was specified
     */
    public boolean hasMetaDataProvider() {
        return metaDataProviderClassName.isPresent();
    }

    /**
     * @return the optional of the {@link MetaDataProvider} class name
     */
    public Optional<String> getMetaDataProviderClassName() {
        return metaDataProviderClassName;
    }

    /**
     * @param metaDataProviderClassName the fully qualified class name of a {@link MetaDataProvider}
     */
    public void setMetaDataProviderClassName(final String metaDataProviderClassName) {
        this.metaDataProviderClassName = Optional.fromNullable(metaDataProviderClassName);
    }

    /**
     * @return true when the specified {@code releaseStage} is not included in the specified
     * {@code notifyReleaseStages}.
     */
    public boolean isStageIgnored() {
        return !notifyReleaseStages.contains(releaseStage);
    }

    /**
     * @param className the fully qualified class name to check
     * @return true when it starts with any of the specified {@code projectPackages}
     */
    public boolean isInProject(final String className) {

        for (final String packageName : projectPackages) {
            if (className.startsWith(packageName)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param className the fully qualified class name to check
     * @return true when the name of the throwable class is being ignored by the specified {@code ignoreClasses}
     */
    public boolean shouldNotifyFor(final String className) {
        return !ignoreClasses.contains(className);
    }

    /**
     * @param key the key to check
     * @return true when the given key is contained in the specified {@code filters}
     */
    public boolean isIgnoredByFilter(final String key) {
        return filters.contains(key);
    }

    /**
     * @return true when all fields are valid
     */
    public boolean isInvalid() {
        return isEndpointInvalid() ||
                isApiKeyInvalid() ||
                isReleaseStageInvalid() ||
                isMetaProviderClassNameInValid();
    }

    /**
     * @param contextAware the {@link ContextAware} implementation to which potential errors will be added
     */
    public void addErrors(final ContextAware contextAware) {
        if (isEndpointInvalid()) {
            contextAware.addError("endpoint must not be null nor empty");
        }

        if (isApiKeyInvalid()) {
            contextAware.addError("apiKey must not be null nor empty");
        }

        if (isReleaseStageInvalid()) {
            contextAware.addError("releaseStage must not be null nor empty");
        }

        if (isMetaProviderClassNameInValid()) {
            contextAware.addError("Could not instantiate class: " + getMetaDataProviderClassName().get() + ". " +
                    "Make sure that you provided the fully qualified class name and that the class has a public " +
                    "accessible default constructor.");
        }
    }

    private boolean isEndpointInvalid() {
        return Strings.isNullOrEmpty(endpoint);
    }

    private boolean isApiKeyInvalid() {
        return Strings.isNullOrEmpty(apiKey);
    }

    private boolean isReleaseStageInvalid() {
        return Strings.isNullOrEmpty(releaseStage);
    }

    private boolean isMetaProviderClassNameInValid() {
        if (metaDataProviderClassName.isPresent()) {
            try {
                final Class<?> metaDataProviderClass = Class.forName(metaDataProviderClassName.get());
                final Constructor<?>[] constructors = metaDataProviderClass.getConstructors();
                final boolean hasNoPublicConstructors = constructors.length == 0;
                final boolean hasNoDefaultConstructor = !containsDefaultConstructor(constructors);
                final boolean doesNotImplementMetaDataProvider = !MetaDataProvider.class.isAssignableFrom(metaDataProviderClass);

                return hasNoPublicConstructors || hasNoDefaultConstructor || doesNotImplementMetaDataProvider;

            } catch (final ClassNotFoundException e) {
                return true;
            }
        }

        return false;
    }

    private boolean containsDefaultConstructor(final Constructor<?>[] constructors) {
        for (final Constructor<?> constructor : constructors) {
            final boolean noParameters = constructor.getParameterTypes().length == 0;
            if (noParameters) {
                return true;
            }
        }

        return false;
    }
} public class MockMetaDataVO extends MetaDataVO {

    public static MockMetaDataVO createMetaDataVO() {
        return new MockMetaDataVO();
    }

    public MockMetaDataVO add(final String tabName, final String key, final Object object) {
        this.addToTab(tabName, key, object);
        return this;
    }
} public class MockNotificationVO extends NotificationVO {

    public static MockNotificationVO createNotificationVO() {
        return new MockNotificationVO();
    }

    public MockNotificationVO add(final EventVO eventVO) {
        getEvents().add(eventVO);
        return this;
    }

    public MockNotificationVO withApiKey(final String apiKey) {
        setApiKey(apiKey);
        return this;
    }
} public class GsonProvider {

    private final Gson gson;

    /**
     * Creates a new instance with the given {@code jsonFilterProvider}.
     *
     * @param jsonFilterProvider the {@link JsonFilterProvider} implementation to use
     */
    public GsonProvider(final JsonFilterProvider jsonFilterProvider) {
        this.gson = new GsonBuilder()
                .registerTypeAdapter(MetaDataVO.class, new MetaDataVOSerializer())
                .registerTypeAdapter(TabVO.class, new TabVOSerializer(jsonFilterProvider))
                .create();
    }

    /**
     * @return a fully configured {@link Gson} instance
     */
    public Gson getGson() {
        return gson;
    }
} public static MockLoggingEvent createLoggingEvent() {
    return new MockLoggingEvent();
} public static MockStackTraceElement createStackTraceElement() {
    return new MockStackTraceElement();
} public static MockThrowableProxy createThrowableProxy() {
    return new MockThrowableProxy();
} public static MockEventVO createEventVO() {
    return new MockEventVO();
} public static MockExceptionVO createExceptionVO() {
    return new MockExceptionVO();
} public static MockNotificationVO createNotificationVO() {
    return new MockNotificationVO();
} public static MockStackTraceVO createStackTraceVO() {
    return new MockStackTraceVO();
}
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Context;
import com.codereligion.bugsnag.logback.Appender;
import com.codereligion.bugsnag.logback.Configuration;
import com.codereligion.bugsnag.logback.mock.model.MockMetaDataVO;
import com.codereligion.bugsnag.logback.mock.model.MockNotificationVO;
import com.codereligion.bugsnag.logback.resource.GsonProvider;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import com.google.gson.Gson;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import static com.codereligion.bugsnag.logback.mock.logging.MockLoggingEvent.createLoggingEvent;
import static com.codereligion.bugsnag.logback.mock.logging.MockStackTraceElement.createStackTraceElement;
import static com.codereligion.bugsnag.logback.mock.logging.MockThrowableProxy.createThrowableProxy;
import static com.codereligion.bugsnag.logback.mock.model.MockEventVO.createEventVO;
import static com.codereligion.bugsnag.logback.mock.model.MockExceptionVO.createExceptionVO;
import static com.codereligion.bugsnag.logback.mock.model.MockNotificationVO.createNotificationVO;
import static com.codereligion.bugsnag.logback.mock.model.MockStackTraceVO.createStackTraceVO;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static org.mockito.Mockito.mock;
/**
 * Copyright 2014 www.codereligion.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.codereligion.bugsnag.logback.integration;


public class SerializationFullIntegrationTest {

    @Rule
    public WireMockRule wireMockRule = new WireMockRule(IntegrationTestConfiguration.PORT);

    private Appender appender = new Appender();

    private final Gson gson = new GsonProvider(new Configuration()).getGson();