package ru.vyarus.dropwizard.orient; import io.dropwizard.Configuration; import io.dropwizard.ConfiguredBundle; import io.dropwizard.jetty.NonblockingServletHolder; import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Environment; import org.hibernate.validator.internal.engine.ValidatorFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.vyarus.dropwizard.orient.configuration.HasOrientServerConfiguration; import ru.vyarus.dropwizard.orient.configuration.OrientServerConfiguration; import ru.vyarus.dropwizard.orient.configuration.deserializer.EntryDeserializer; import ru.vyarus.dropwizard.orient.configuration.deserializer.NetworkProtocolDeserializer; import ru.vyarus.dropwizard.orient.configuration.deserializer.ParameterDeserializer; import ru.vyarus.dropwizard.orient.health.OrientServerHealthCheck; import ru.vyarus.dropwizard.orient.internal.DummyTraversableResolver; import ru.vyarus.dropwizard.orient.internal.EmbeddedOrientServer; import ru.vyarus.dropwizard.orient.support.ConsoleCommand; import ru.vyarus.dropwizard.orient.support.OrientServlet; import java.lang.reflect.Field; /** * Bundle starts embedded orient server. Application configuration object must implement * {@code HasOrientConfiguration}, which provides orient configuration object. * <p>In configuration (yaml) orient server configuration (orient own config) could be defined * as reference to xml file (orient default format) or directly in main config (by converting xml to yaml). * Server startup could be disabled by setting 'start: false' in config.</p> * <p>Additionally registers console command.</p> * <p>Important note: orient object database conflicts with hibernate-validator, because orient brings jpa classes, * which activates hibernate lazy properties checks in validator. Bundle implicitly fixes this by overriding * TraversableResolver. It's completely safe for application if you don't use hibernate.</p> * NOTE: server will not start when console command called, because dropwizard will not run managed objects this time * (only server command triggers managed objects lifecycle). But plocal connections still could be used. * Also, if server already started, then you can use remote connections. * * @param <T> configuration type */ @SuppressWarnings("checkstyle:ClassDataAbstractionCoupling") public class OrientServerBundle<T extends Configuration & HasOrientServerConfiguration> implements ConfiguredBundle<T> { private final Logger logger = LoggerFactory.getLogger(OrientServerBundle.class); private final Class<T> configClass; /** * @param configClass configuration class */ public OrientServerBundle(final Class<T> configClass) { this.configClass = configClass; } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public void initialize(final Bootstrap<?> bootstrap) { // support shorter configuration bootstrap.getObjectMapper().addHandler(new EntryDeserializer()); bootstrap.getObjectMapper().addHandler(new ParameterDeserializer()); bootstrap.getObjectMapper().addHandler(new NetworkProtocolDeserializer()); recoverValidatorBehaviour(bootstrap); bootstrap.addCommand(new ConsoleCommand(configClass)); } /** * {@inheritDoc} */ @Override public void run(final T configuration, final Environment environment) throws Exception { final OrientServerConfiguration conf = configuration.getOrientServerConfiguration(); if (conf == null || !conf.isStart()) { logger.debug("Orient server start disabled. Set 'start: true' in configuration to enable."); return; } final EmbeddedOrientServer orientServer = new EmbeddedOrientServer(conf, environment.getObjectMapper()); environment.lifecycle().manage(orientServer); environment.healthChecks().register("orient-server", new OrientServerHealthCheck()); if (conf.isAdminServlet()) { environment.getAdminContext().addServlet(new NonblockingServletHolder( new OrientServlet(orientServer.getServerInfo())), "/orient/*"); } } /** * Orientdb-object module includes hiberate-jpa-api into classpath and hibernate-validator * starts to think that JPA is available (if jpa available then validated objects must be checked with * traversable provider to avoid lazy init exceptions). * But orient's OJPAPersistenceProvider.getProviderUtil() simply throws exception. * So the only way to resolve problem is to substitute traversableProvider with dummy one. * It's not a hack: in normal case, validator also use dummy impl and only if * javax.persistence.Persistence class found in classpath use complete impl.. so we just correct * behaviour here. * Note that it can't cause side effects because hibernate is actually not used. */ private void recoverValidatorBehaviour(final Bootstrap<?> bootstrap) { logger.debug("Replacing TraversableResolver to fix hibernate validator"); final ValidatorFactoryImpl factory = (ValidatorFactoryImpl) bootstrap.getValidatorFactory(); try { final Field field = factory.getClass().getDeclaredField("traversableResolver"); field.setAccessible(true); field.set(factory, new DummyTraversableResolver()); field.setAccessible(false); } catch (Exception e) { throw new IllegalStateException("Failed to substitute traversableResolver", e); } } }