1

I'm using Spring-Data with Spring-Data MongoDB to map an entity class using the @CompoundIndexes annotation in which I'm specifying indexes with both names and their definitions. In my production environment, I decided I needed to change some of the attributes of the index based on the actual data. Now every time my application starts up, it fails to load because a failure to create an index matching the specification in the annotation results and that exception is thrown during the initialization process (as seen below).

Is there anyway of configuring spring data and mongodb so that these exceptions are logged but do not fail the start of the container?

Exception while creating index
! com.mongodb.MongoCommandException: Command failed with error 86: 'Trying to create an index with same name event_source_link_type_at_id_IDX with different key spec **** vs existing spec *****' on server 127.0.0.1:27017. The full response is { "ok" : 0.0, "errmsg" : "Trying to create an index with same name event_source_link_type_at_id_IDX with different key spec **** vs existing spec *****", "code" : 86 }
! at com.mongodb.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:115) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.connection.CommandProtocol.execute(CommandProtocol.java:114) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:159) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.connection.DefaultServerConnection.executeProtocol(DefaultServerConnection.java:286) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.connection.DefaultServerConnection.command(DefaultServerConnection.java:173) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.operation.CommandOperationHelper.executeWrappedCommandProtocol(CommandOperationHelper.java:215) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.operation.CommandOperationHelper.executeWrappedCommandProtocol(CommandOperationHelper.java:198) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.operation.CommandOperationHelper.executeWrappedCommandProtocol(CommandOperationHelper.java:170) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.operation.CreateIndexesOperation$1.call(CreateIndexesOperation.java:116) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.operation.CreateIndexesOperation$1.call(CreateIndexesOperation.java:111) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.operation.OperationHelper.withConnectionSource(OperationHelper.java:230) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.operation.OperationHelper.withConnection(OperationHelper.java:221) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.operation.CreateIndexesOperation.execute(CreateIndexesOperation.java:111) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.operation.CreateIndexesOperation.execute(CreateIndexesOperation.java:66) ~[mongodb-driver-core-3.2.2.jar:na]
! at com.mongodb.Mongo.execute(Mongo.java:781) ~[mongodb-driver-3.2.2.jar:na]
! at com.mongodb.Mongo$2.execute(Mongo.java:764) ~[mongodb-driver-3.2.2.jar:na]
! at com.mongodb.DBCollection.createIndex(DBCollection.java:1541) ~[mongodb-driver-3.2.2.jar:na]
! at org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator.createIndex(MongoPersistentEntityIndexCreator.java:142) [spring-data-mongodb-1.8.4.RELEASE.jar:na]

1 Answer 1

4

Turns out this isn't as easy as I thought but can be done with a few extra classes.

First you will need to override the class that creates the indexes and overwrite the method that creates the indexes, trapping the exceptions that match and only logging them.

Unfortunately it is package protected so you will need to create a class in the same package as the class we're extending.

package org.springframework.data.mongodb.core.index;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;

public class ExceptionIgnoringIndexCreator extends MongoPersistentEntityIndexCreator {

    //assuming SLF4J as your logger otherwise, put your logger here
    private static final Logger LOG = LoggerFactory.getLogger(ExceptionIgnoringIndexCreator.class);

    public ExceptionIgnoringIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory) {
        super(mappingContext, mongoDbFactory);
    }

    @Override
    void createIndex(MongoPersistentEntityIndexResolver.IndexDefinitionHolder indexDefinition) {
        try {
            super.createIndex(indexDefinition);
        } catch (final RuntimeException exp) {
            final RuntimeException trans = translate(exp);
            if (trans != null) {
                throw trans;
            } else {
                LOG.warn("Exception while creating index", exp);
            }
        }
    }

    protected RuntimeException translate(final RuntimeException exp) {
        if (exp == null || exp.getMessage().contains("Cannot create index")) {
            return null;
        }
        return exp;
    }
}

The index creation is triggered by an event published by the MongoMappingContext using the ApplicationEventPublisherInterface. We need a class that we can lazily set another ApplicationEventPublisher as a delegate that the two methods of the interface will delegate their calls to.

public class DelegatingPublisher implements ApplicationEventPublisher {

        private ApplicationEventPublisher delegate;

        @Override
        public void publishEvent(ApplicationEvent event) {
            delegate.publishEvent(event);
        }

        @Override
        public void publishEvent(Object event) {
            delegate.publishEvent(event);
        }

        public void setDelegate(ApplicationEventPublisher delegate) {
            this.delegate = delegate;
        }

}

Normally you configure spring data mongoDB using a class that extends AbstractMongoConfig and overrides the "mongo()" method.

The component responsible for publishing the message that initializes the indexes is the one returned from "mongoMappingContext()" so you'll need to override that method and extend the default MongoMappingContext overriding the method that sets the event publisher and passing our new delegate publisher in its place.

@Configuration
@EnableMongoRepositories("com.my.company")
public class MyMongoConfig extends AbstractMongoConfiguration {
...
    @Override
    @Bean
    public MongoMappingContext mongoMappingContext() throws ClassNotFoundException {
        final DelegatingPublisher dep = new DelegatingPublisher();
        final MongoMappingContext mappingContext = new MongoMappingContext() {
            @Override
            public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
                super.setApplicationEventPublisher(dep);
            }
        };
        mappingContext.setInitialEntitySet(getInitialEntitySet());
        mappingContext.setSimpleTypeHolder(customConversions().getSimpleTypeHolder());
        mappingContext.setFieldNamingStrategy(fieldNamingStrategy());

        try {
            final MongoPersistentEntityIndexCreator indexCreator = new ExceptionIgnoringIndexCreator(mappingContext, mongoDbFactory());
            dep.setDelegate(new MongoMappingEventPublisher(indexCreator));
            return mappingContext;
        } catch (Exception exp) {
            throw new RuntimeException(exp);
        }
    }
...
}

If you use XML based configuration, you'll need one more class and the specified configuration

public class EventDelegatingMongoMappingContext extends MongoMappingContext {

    private ApplicationEventPublisher publisher;

    public EventDelegatingMongoMappingContext(ApplicationEventPublisher publisher) {
        this.publisher = publisher;    
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        super.setApplicationEventPublisher(publisher);
    }
}
<mongo:db-factory id="mongoDbFactory"
                  host="localhost"
                  port="27017"
                  dbname="database"
                  username="mycompany"
                  password="secret"/>

<bean id="delegatingPublisher" class="com.my.company.DelegatingPublisher">
    <property name="delegate" ref="mappingEventPublisher" />
</bean>

<!-- Must be named 'mongoMappingContext' to be recognized up -->
<bean id="mongoMappingContext" class="com.my.company.EventDelegatingMongoMappingContext">
    <constructor-arg>
        <bean ref="delegatingPublisher" />
    </constructor-arg>
</bean>

<bean id="mongoIndexCreator" class="org.springframework.data.mongodb.core.index.ExceptionIgnoringIndexCreator">
    <constructor-arg>
        <bean ref="mongoMappingContext"/>
    </constructor-arg>
    <constructor-arg>
        <bean ref="mongoDbFactory"/>
    </constructor-arg>
</bean>

<bean id="mappingEventPublisher" class="org.springframework.data.mongodb.core.index.MongoMappingEventPublisher">
    <constructor-arg>
        <bean ref="mongoIndexCreator"/>
    </constructor-arg>
</bean>
Sign up to request clarification or add additional context in comments.

1 Comment

Happy to be the first person to upvote this answer. Thanks for taking the time, going to be very useful for me in addressing my issue, described here: stackoverflow.com/questions/52334716/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.