Статьи

Развертывание MongoDB Morphia ODM в OSGi

Morphia — это библиотека ODM для MongoDB. Это может рассматриваться как облегченная альтернатива Spring Data MongoDB или как более богатая версия Jongo . По сравнению с Spring Data MongoDB, Morphia проще настроить в среде, отличной от Spring, и у нее гораздо меньше зависимостей. С другой стороны, ему не хватает унифицированного интерфейса репозитория Spring Data и подхода, основанного на соглашении о конфигурации, который может повысить производительность после настройки базовой конфигурации.

JAR ядра Morphia готов к OSGi. Однако включение разрешения класса сущностей в Morphia при работе в OSGi требует некоторых дополнительных шагов.

Класс погрузки в Морфию

Подход Morphia к разрешению классов сущностей является расширяемым. Он делегирует конструкцию объекта экземпляру, который реализует интерфейс org.mongodb.morphia.ObjectFactory . Morphia также предоставляет реализацию по умолчанию — org.mongodb.morphia.mapping.DefaultCreator , которая с помощью необработанного объекта com.mongodb.DBObject читает свое поле ‘className’, разрешает класс и создает соответствующий POJO.

DefaultCreator делегирует разрешение класса отдельному защищенному методу, который использует загрузчик классов контекста потока (TCCL). Хотя TCCL обычно должен знать о классах сущностей, поскольку он обычно вызывается с уровня DAO, это не всегда так. Например, в Karaf во время создания экземпляра контейнера Blueprint TCCL является не инициализатором загрузчика класса связки, а загрузчиком класса начальной загрузки Karaf. Переопределение метода загрузки классов и делегирование загрузчику классов с поддержкой OSGi обеспечивает более согласованное решение.

Разрешение класса сущностей в OSGi с Morphia

Вот настроенная версия DefaultCreator, которая заменяет загрузчик классов TCCL на загрузчик классов контекста пакета:

public class BundleObjectFactory extends DefaultCreator {
	private BundleContext bundleContext;
	
	@Override
	protected ClassLoader getClassLoaderForClass() {
		ClassLoader cl = ((BundleWiring)bundleContext.getBundle().adapt(
			BundleWiring.class)).getClassLoader();
		
		return cl;
	}

	public BundleObjectFactory(BundleContext bundleContext) {
		super();
		this.bundleContext = bundleContext;
	}
}

Настроенный экземпляр класса Morphia можно создать с помощью фабрики:

public class MorphiaFactory {
	private BundleContext bundleContext;	
	private List<String> classes;
	
	private Class<?> loadClass(String className) throws ClassNotFoundException {
		ClassLoader cl = ((BundleWiring)bundleContext.getBundle().adapt(
			BundleWiring.class)).getClassLoader();
		return cl.loadClass(className);
	}
	
	private Mapper getMapper() {
		Mapper mapper = new Mapper();	
		mapper.getOptions().objectFactory = new BundleObjectFactory(bundleContext);		
		return mapper;
	}
	
	private List<Class<?>> getClassObjects(List<String> classNames) 
		throws ClassNotFoundException {
		if (classNames == null || classNames.size() == 0)
			return null;
		
		List<Class<?>> classObjs = new ArrayList<>();
		for (String className:classNames) {
			classObjs.add(loadClass(className));
		}		
		return classObjs;
	}
	
	@SuppressWarnings("rawtypes")
	public Morphia get() throws ClassNotFoundException {
		Mapper mapper = getMapper();	
		List<Class<?>> classObjs = getClassObjects(classes);
		
		Morphia morphia = new Morphia(mapper, new HashSet<Class>(classObjs));	
		return morphia;
	}

	public void setBundleContext(BundleContext bundleContext) {
		this.bundleContext = bundleContext;
	}

	public void setClasses(List<String> classes) {
		this.classes = classes;
	}
}

Настройка Blueprint

Собрав все это вместе, используя Blueprint для создания экземпляров и подключения, получаем следующую конфигурацию:

<blueprint default-activation="eager"
	xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0">
	
	<cm:property-placeholder persistent-id="io.modio.blog.osgi.morphia"/>

	<bean id="mongoClient" class="com.mongodb.MongoClient">
		<argument value="${io.modio.blog.osgi.morphia.mongodb.host}"/>
		<argument value="${io.modio.blog.osgi.morphia.mongodb.port}"/>
	</bean>
	
	<bean id="morphiaFactory" class="io.modio.blog.osgi.morphia.MorphiaFactory">
		<property name="bundleContext" ref="blueprintBundleContext"/>
		<property name="classes">
			<list>
				<value>io.modio.blog.osgi.morphia.entity.Acl</value>
				<value>io.modio.blog.osgi.morphia.Principal</value>
			</list>
		</property>
	</bean>
	<bean id="morphia" factory-ref="morphiaFactory" factory-method="get"/>
	
	<bean id="datastoreFactory" class="io.modio.blog.osgi.morphia.DatastoreFactory">
		<property name="mongoClient" ref="mongoClient"/>
		<property name="morphia" ref="morphia"/>
		<property name="database" value="${io.modio.blog.osgi.morphia.mongodb.db}"/>
	</bean>
	<bean id="datastore" factory-ref="datastoreFactory" factory-method="get"/>
	
	<!-- DAOs -->	
	<bean id="aclDao" class="io.modio.blog.osgi.morphia.dao.AclDaoImpl">
		<argument ref="datastore"/>
	</bean>
	<bean id="principalDao" class="io.modio.blog.osgi.morphia.dao.PrincipalDaoImpl">
		<argument ref="datastore"/>
	</bean>
        
	<!-- Services -->
	<service ref="aclDao" interface="io.modio.blog.osgi.morphia.dao.AclDao"/>	
	<service ref="principalDao" interface="io.modio.blog.osgi.morphia.dao.PrincipalDao"/>
</blueprint>

Приведенная выше конфигурация включает в себя два примера DAO, которые реализуют простую службу аутентификации / авторизации с использованием MongoDB в качестве постоянного хранилища.

Реализация DatastoreFactory проста:

public class DatastoreFactory {
	private MongoClient mongoClient;	
	private Morphia morphia;
	private String database;
	
	public Datastore get() {
		return morphia.createDatastore(mongoClient, database);
	}

	/** get/set methods */
	...
}

Особенность Morphia Karaf

При развертывании в Karaf полезно сгруппировать все зависимости Morphia в одну повторно используемую функцию, на которую затем можно ссылаться из других функций:

<feature name="morphia" version="0.108" resolver="(obr)">
	<feature version="[1.9.13,2)">jackson</feature>
		
	<bundle dependency="true">mvn:org.mongodb/mongo-java-driver/2.12.2</bundle>
	<bundle>wrap:mvn:com.thoughtworks.proxytoys/proxytoys/1.0</bundle>
	<bundle>mvn:org.mongodb.morphia/morphia/0.108</bundle>
</feature>