ServiceMix не может найти источник данных OSGI
Я погрузился в ServiceMix 5.4.0 и OSGi, и столкнулся с довольно странным поведением с OpenJPA.
У меня есть источник данных, определенный так:
<blueprint
xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.postgresql.Driver"/>
<property name="url" value="jdbc:postgresql://localhost:5432/test"/>
<property name="username" value="test"/>
<property name="password" value="test"/>
</bean>
<service interface="javax.sql.DataSource" ref="dataSource">
<service-properties>
<entry key="osgi.jndi.service.name" value="jdbc/test"/>
</service-properties>
</service>
</blueprint>
Используя команду jndi:names, я могу убедиться, что источник данных видим:
karaf@root> jndi:names
JNDI Name Class Name
osgi:service/jndi org.apache.karaf.jndi.internal.JndiServiceImpl
osgi:service/jdbc/test org.apache.commons.dbcp.BasicDataSource
karaf@root>
Мой файл persistence.xml:
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="test" transaction-type="JTA">
<jta-data-source>osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/test)</jta-data-source>
<class>com.example.persistence.security.User</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="openjpa.jdbc.DBDictionary" value="postgres"/>
<property name="openjpa.Log" value="slf4j"/>
</properties>
</persistence-unit>
</persistence>
Затем я внедряю модуль персистентности в класс DAO через Blueprint:
<?xml version="1.0" encoding="UTF-8"?>
<blueprint default-activation="eager"
xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:jpa="http://aries.apache.org/xmlns/jpa/v1.0.0"
xmlns:tx="http://aries.apache.org/xmlns/transactions/v1.0.0">
<bean id="securityDAO" class="com.example.security.dao.SecurityDAOImpl" init-method="init">
<tx:transaction method="*" value="Required" />
<jpa:context property="entityManager" unitname="test" />
</bean>
<service ref="securityDAO" interface="com.example.security.dao.SecurityDAO">
</service>
</blueprint>
Персистентный модуль успешно введен, что я проверяю в init-методе DAO:
public void init() {
if (em==null) {
log.error("Entity manager not found. Check JPA configuration.");
throw new RuntimeException("No EntityManager found");
}
log.info("Started SecurityDAO");
}
После всей моей усердной работы ServiceMix вознаграждает меня следующим загадочным исключением, когда я вызываю метод моего DAO из другого компонента:
....
public void setSecurityDAO (SecurityDAO dao) {
this.dao = dao;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userName = req.getParameter("userName");
String password = req.getParameter("password");
// Invocation of injected DAO results in exception
User u = dao.authenticateUser(userName, password);
Это приводит к следующему:
Caused by: java.lang.RuntimeException: The DataSource osgi:service/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/test) required by bundle persistence/0.0.1.SNAPSHOT could not be found.
at org.apache.aries.jpa.container.unit.impl.JndiDataSource.getDs(JndiDataSource.java:87)
at org.apache.aries.jpa.container.unit.impl.DelayedLookupDataSource.getConnection(DelayedLookupDataSource.java:36)
at org.apache.openjpa.lib.jdbc.DelegatingDataSource.getConnection(DelegatingDataSource.java:116)
at org.apache.openjpa.lib.jdbc.DecoratingDataSource.getConnection(DecoratingDataSource.java:93)
at org.apache.openjpa.jdbc.schema.DataSourceFactory.installDBDictionary(DataSourceFactory.java:233)
... 54 more
Caused by: javax.naming.NoInitialContextException: Unable to find the InitialContextFactory org.eclipse.jetty.jndi.InitialContextFactory.
at org.apache.aries.jndi.ContextHelper.getInitialContext(ContextHelper.java:148)
at org.apache.aries.jndi.OSGiInitialContextFactoryBuilder.getInitialContext(OSGiInitialContextFactoryBuilder.java:49)
at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:684)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:313)
at javax.naming.InitialContext.init(InitialContext.java:244)
at javax.naming.InitialContext.<init>(InitialContext.java:216)
at org.apache.aries.jpa.container.unit.impl.JndiDataSource.getDs(JndiDataSource.java:64)
... 58 more
Каким-то образом экспортированный в OSGi источник данных не попадает в постоянный пакет. Странная часть в том, что когда я добавил следующий код в метод init, чтобы посмотреть, смогу ли я выполнить тестовый запрос, OpenJPA не только не выдает исключение в методе init, но и вызывает DAO, который теперь вызывал исключение. работает так же:
public void init() {
if (em==null) {
log.error("Entity manager not found. Check JPA configuration.");
throw new RuntimeException("No EntityManager found");
}
try {
Query q = em.createNativeQuery("SELECT 1=1");
q.getFirstResult();
} catch (Exception ex) {
log.error("Unable to execute test query against database", ex);
throw new RuntimeException(ex);
}
log.info("Started SecurityDAO");
}
Итак, подведем итог: если я вызываю метод из другого пакета, отличного от моего DAO, OpenJPA выдает исключение, указывающее, что он не может найти InitialNamingContext, и не показывает никаких признаков в журнале, что он запущен. Если я выполняю запрос внутри моего DAO до того, как внешний компонент вызывает его, каким-то образом OpenJPA может найти InitialNamingContext, OpenJPA появляется в журнале, и последующие вызовы извне DAO начинают работать.
Очевидно, я здесь упускаю что-то простое. Буду очень признателен за любую помощь или вдумчивое объяснение того, что ломает, или что я делаю неправильно.
РЕДАКТИРОВАТЬ:
Я не заметил прошлой ночью, но когда я добавил тестовый запрос, в журнале появились следующие строки. Они отсутствуют, когда я закомментирую этот запрос:
... | Runtime | 220 - org.apache.openjpa - 2.3.0 | Starting OpenJPA 2.3.0
... | JDBC | 220 - org.apache.openjpa - 2.3.0 | Using dictionary class "org.apache.openjpa.jdbc.sql.PostgresDictionary".
... | JDBC | 220 - org.apache.openjpa - 2.3.0 | Connected to PostgreSQL version 9.9 using JDBC driver PostgreSQL Native Driver version PostgreSQL 9.3 JDBC4.1 (build 1102).
РЕДАКТИРОВАТЬ 2:
Попробовал его на простой ванили Караф 3.0.3, и получил такую же ошибку. В качестве обходного пути я создал отдельный bean-компонент в комплекте, который выполняет вышеупомянутый тестовый запрос. По-видимому, до тех пор, пока один компонент в пакете выполняет вызов OpenJPA до того, как компонент вне пакета пытается выполнить вызов, OpenJPA будет правильно инициализирован.
Поскольку это нигде не упоминается в документах OpenJPA/ServiceMix, я могу только предположить, что я делаю что-то не так в других местах моей конфигурации.
РЕДАКТИРОВАТЬ 3:
Пер Джон Форт, вот Манифест.МФ
Manifest-Version: 1.0
Bnd-LastModified: 1430533396366
Build-Jdk: 1.8.0_45
Built-By: somedude
Bundle-Blueprint: OSGI-INF/blueprint/blueprint.xml
Bundle-Description: Database access layer for Peer Review product
Bundle-ManifestVersion: 2
Bundle-Name: Example :: Persistence
Bundle-SymbolicName: persistence-jpa
Bundle-Version: 0.0.1.SNAPSHOT
Created-By: Apache Maven Bundle Plugin
Export-Package: com.example.persistence.security;version="0.0.1.SNAPSHOT",co
m.example.security.dao;version="0.0.1.SNAPSHOT";uses:="com.example.persistence.
security,javax.persistence"
Export-Service: com.example.security.dao.SecurityDAO
Import-Package: javax.persistence;version="[1.1,2)",org.osgi.service.blu
eprint;version="[1.0.0,2.0.0)",org.slf4j;version="[1.7,2)"
Meta-Persistence: META-INF/persistence.xml
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.7))"
Tool: Bnd-2.3.0.201405100607
И, поскольку это может быть связано, pom.xml пакета JPA:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>example</artifactId>
<groupId>com.example</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>persistence-jpa</artifactId>
<packaging>bundle</packaging>
<name>Example :: Persistence</name>
<dependencies>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jpa_2.0_spec</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.5.3</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Meta-Persistence>META-INF/persistence.xml</Meta-Persistence>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Bundle-Version>${project.version}</Bundle-Version>
<Import-Package>*</Import-Package>
<Export-Package>com.example.persistence*,com.example.security.*;version=${project.version}</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
1 ответ
Если вы используете OSGI, видимость класса определяется в файлах MANIFEST.MF.
Таким образом, пакет персистентности может видеть и загружать только классы, импортированные в его файл MANIFEST.MF.
Правильный способ расширения существующего пакета - определить фрагмент, который присоединен к существующему пакету. Таким образом, вы можете предоставить классы (например, DAO) и файлы (например, persistence.xml) и сделать их видимыми для фрагмента-хоста.
MANIFEST.MF выглядит следующим образом
Bundle-ManifestVersion: 2
Bundle-Name: foo.bar.openjpa-fragment
Bundle-SymbolicName: foo.bar.openjpa-fragment;singleton:=true
Bundle-Version: 0.0.1.SNAPSHOT
Bundle-Vendor: foo bar
Fragment-Host: org.apache.openjpa-bundle
Bundle-ClassPath: .
Обратите внимание, что это только пример.
OSGI означает обеспечение надлежащей видимости.
Вы можете добавить более одного фрагмента в существующий пакет, например, чтобы сохранить конфигурацию в отдельном пакете, что облегчает переключение конфигурации.