Meglévő technológiák OSGi integrációja
Előadó: Csákány Róbert
Bemutatkozás
-
2009 óta foglalkozom OSGi-al
-
Sikeres enterprise alkalmazásfejlesztés (AVON Cosmetics MAPS rendszer)
-
OSGi alapú keretrendszer fejlesztés (JUDO)
-
Nyílt forráskódú fejlesztés (liveSense)
-
OSGi tanácsadás GE HealthCare részére
-
Github: https://github.com/robertcsakany
A témák
-
Az esszencia: Class Loading mechanizmus
-
Nem minden JAR OSGI bundle
-
Bundle is meg nem is...
-
Hogyan érdemes OSGi-ban fejleszteni?
Az esszencia: ClassLoading mechanizmus
Mitől lesz bundle a JAR-ból? - MANIFEST.MF file
Manifest-Version: 1.0
Bnd-LastModified: 1466006904163
Build-Jdk: 1.7.0_79
Built-By: robson
Bundle-Category: judo
Bundle-Description: Superpom: contains the configuration of the maven pl
ugins.
Bundle-ManifestVersion: 2
Bundle-Name: rdbms-hsqldb-server
Bundle-SymbolicName: hu.blackbelt.judo.rdbms.hsqldb-server
Bundle-Version: 1.0.0.SNAPSHOT
Created-By: Apache Maven Bundle Plugin
Export-Package: hu.blackbelt.rdbms.hsqldb.server;uses:="org.osgi.service
.cm,org.osgi.service.component,org.slf4j";version="1.0.0"
Import-Package: org.apache.sling.commons.osgi;version="[2.2,3)",org.hsql
db,org.osgi.service.cm;version="[1.4,2)",org.osgi.service.component;ver
sion="[1.2,2)",org.slf4j;version="[1.7,2)",org.joda.time.convert;versio
n="[2.8,3)",org.joda.time.base;version="[2.8,3)",org.joda.time.tz;versi
on="[2.8,3)",org.joda.time.chrono;version="[2.8,3)",org.joda.time.field
;version="[2.8,3)",org.joda.time.format;version="[2.8,3)"
Judo-Platform: true
Require-Capability: osgi.service;effective:=active;filter:="(objectClass
=org.osgi.service.cm.ConfigurationAdmin)",osgi.ee;filter:="(&(osgi.ee=J
avaSE)(version=1.7))"
Service-Component: OSGI-INF/hu.blackbelt.rdbms.hsqldb.server.HsqldbServe
r.xml
Tool: Bnd-2.3.0.201405100607
Az esszencia: ClassLoading mechanizmus
Az esszencia: ClassLoading mechanizmus
Nincs egyésges recept a csomagolásra
Nem minden JAR OSGi bundle
-
Minden JAR külön bundle-ba kerül - OSGi filózófiához ez áll legközelebb. - Import exportok kezelése.
-
Több JAR-t csomagolhatunk egy bundle-ba - A bundlen belül reflection-el történik az osztályok töltése és statikus modellben került a rendszer felépítésre.
-
Proxy Classloader használata (Apache Sling JSP)
-
Dynamic import használata - mikor már más lehetőségünk nincs
-
A Bundle-ban a Factory class-ok felüldefiniálása
OSGI metaadatok hozzáadása MAVEN-el
Nem minden JAR OSGi bundle
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.5.3</version>
<extensions>true</extensions>
<configuration>
<instructions>
<_exportcontents>
org.apache.poi.*;version=${poi.version},
org.openxmlformats.schemas.*;version=${poi.schema.version},
schemasMicrosoftComOfficeExcel.*;version=${poi.schema.version},
schemasMicrosoftComOfficeOffice.*;version=${poi.schema.version},
schemasMicrosoftComOfficePowerpoint.*;version=${poi.schema.version},
schemasMicrosoftComVml.*;version=${poi.schema.version},
org.etsi.uri.*;version=${poi.security.version}
</_exportcontents>
<Import-Package>
com.sun.javadoc;resolution:=optional,
com.sun.tools.javadoc;resolution:=optional,
org.apache.crimson.jaxp;resolution:=optional,
org.apache.tools.ant;resolution:=optional,
org.apache.tools.ant.taskdefs;resolution:=optional,
org.apache.tools.ant.types;resolution:=optional,
junit.framework.*;resolution:=optional,
junit.textui.*;resolution:=optional,
org.junit.*;resolution:=optional,
org.apache.xml.security.*;resolution:=optional,
org.apache.jcp.xml.dsig.internal.dom.*;resolution:=optional,
*
</Import-Package>
<DynamicImport-Package>
org.apache.xmlbeans.*,
schemaorg_apache_xmlbeans.*
</DynamicImport-Package>
<!-- bundle supplied resource prefixes -->
<Include-Resource>{maven-resources}</Include-Resource>
<!-- Do not inline jars, include as jar files -->
<!-- There are config files with same name will be overwritten -->
<Embed-Dependency>*;scope=compile;inline=false</Embed-Dependency>
</instructions>
</configuration>
</plugin>
Bundle is meg nem is...
-
WSO2 - Minden extension mint fragment bundle telepthető - Nincsenek service-k, nincsenek igazi importok / exportok
-
JBoss Activity - ugyanazok a packagek több JAR-ban megtalálhatóak. OSGI támogatja a split-package-t, de használata nem ajánlott.
-
Eclipse pluginek rendszeresen Required-Bundle-t használnak
-
Számtalan bundle ClassNotFound problémára a Dynamic Import Package-t haszmálja
Nem mindenki használja az OSGi-t az ajánlásoknak megfelelően
Hogyan érdemes csinálni?
Hogyan érdemes OSGi-ba szervezni
- 3rd paty bundle - Ez tartalmazza 1 vagy több bundleban a 3rd party classokat illetve exportálja azokat a packageket, amelyek a használathoz kellenek. Általában service regisztrációkat nem tartalmaznak, de aktivátorokat tartalmazhat, például regisztrációk Factory-ba.
- API Bundle - Az interface-k kódját tartalmazza, amely már egy absztraktabb formája az integrált frameworknek. Amennyiben már maga a framework külön definiálja az API interfaceit számunkra megfelelő formában, akkor a saját API bundle nem szükéseges.
Hogyan érdemes OSGi-ba szervezni
- Service Bundle - Ez tartalmazza az API implementációját vagy azt a kódot, ami az API interface-re regisztrálja a kipléldányostott osztályt. Deklaratív service esetén ezt a Service Component Library végzi a ConfigAdmin servicbe regisztált konfiguráció alapján - ez kerül a ServiceRegistry-be.
- Configok - Ezek alapértelmezetten az osztályok full qualified neve alapján képzett CFG fileok.
Hogyan érdemes OSGi-ba szervezni
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Export-Package>
com.octo.captcha.*;version:=1.0
</Export-Package>
<Embed-Dependency>
jcaptcha,
jcaptcha-api,
filters,
imaging
</Embed-Dependency>
<Import-Package>
EDU.oswego.*;resolution:=optional,
com.sun.speech.*;resolution:=optional,
net.sf.ehcache.*;resolution:=optional,
org.acegisecurity.*;resolution:=optional,
org.apache.struts.*;resolution:=optional,
org.apache.velocity.*;resolution:=optional,
org.quartz.*;resolution:=optional,
org.roller.*;resolution:=optional,
org.springframework.*;resolution:=optional,
*
</Import-Package>
</instructions>
</configuration>
</plugin>
3rd party bundle
Hogyan érdemes OSGi-ba szervezni
package org.liveSense.service.captcha;
import java.awt.image.BufferedImage;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface CaptchaService {
public BufferedImage getCaptchaImage(String captchaID, Locale locale);
public boolean validateCapthaResponse(String captchaID, String response);
public boolean validateCaptchaResponse(HttpServletRequest request, String response);
public String extractCaptchaIdFromRequest(HttpServletRequest request);
public void setCaptchaId(HttpServletRequest request,
HttpServletResponse response, String captchaId);
public BufferedImage getCaptchaImage(String captchaEngineName, String captchaID, Locale locale);
public boolean validateCaptchaResponse(String captchaEngineName, String captchaID, String response);
public boolean validateCaptchaResponse(String captchaEngineName, HttpServletRequest request, String response);
public void setCaptchaId(String captchaEngineName, HttpServletRequest request,
HttpServletResponse response, String captchaId);
}
API bundle
Hogyan érdemes OSGi-ba szervezni
package org.liveSense.service.captcha;
import java.awt.image.BufferedImage;
import java.util.Locale;
public interface CaptchaEngine {
public boolean validateResponse(String id, String response);
public BufferedImage getImage(String id, String text, Locale locale);
public void init();
public void close();
public String getName();
}
API bundle
Hogyan érdemes OSGi-ba szervezni
@Component(label = "%captcha.service.name", description = "%captcha.service.description", immediate = false, metatype = true)
@Properties(value={
@Property(label="%captcha.service.defaultengine", description="%captcha.service.defaultengine.description", name = CaptchaServiceImpl.PAR_CAPTHA_ENGINE, value = CaptchaServiceImpl.DEFAULT_CAPTCHA_ENGINE),
@Property(label="%captcha.service.storage",description="%captcha.service.storage.description", name = CaptchaServiceImpl.PAR_STORAGE, value = CaptchaServiceImpl.DEFAULT_STORAGE, options = {
@PropertyOption(name = CaptchaServiceImpl.STORAGE_COOKIE, value = "Cookie"),
@PropertyOption(name = CaptchaServiceImpl.STORAGE_SESSION_ATTRIBUTE, value = "Session Attribute")
})
})
@References(value={
@Reference(cardinality=ReferenceCardinality.MANDATORY_MULTIPLE, policy=ReferencePolicy.DYNAMIC, strategy=ReferenceStrategy.EVENT, bind="bindEngine", unbind="unBindEngine", referenceInterface=CaptchaEngine.class)
})
@Service
public class CaptchaServiceImpl implements CaptchaService {
Logger log = LoggerFactory.getLogger(CaptchaServiceImpl.class);
/**
* The value of the parameter indicating the use of a Cookie to store the
* authentication data.
*/
public static final String STORAGE_COOKIE = "cookie";
/**
* The value of the parameter indicating the use of a session attribute to
* store the authentication data.
*/
public static final String STORAGE_SESSION_ATTRIBUTE = "session";
/**
* To be used to determine if the chalange ID comes from a cookie or from a
* session attribute.
*/
public static final String DEFAULT_STORAGE = STORAGE_COOKIE;
public static final String PAR_STORAGE = "captcha.storage";
public static final String DEFAULT_CAPTCHA_ENGINE = "JCaptchaDefault";
public static final String PAR_CAPTHA_ENGINE = "captcha.engine.default";
public static final String PAR_CAPTCHA_ATTRIBUTE_NAME = "captcha.uid";
private String authStorageType = DEFAULT_STORAGE;
private String defaultCapthaEngineName = DEFAULT_CAPTCHA_ENGINE;
@Reference(cardinality=ReferenceCardinality.MANDATORY_UNARY, policy=ReferencePolicy.DYNAMIC)
Configurator configurator;
CaptchaEngine defaultEngine;
Map<String, CaptchaEngine> captchaEngines = new ConcurrentHashMap<String, CaptchaEngine>();
protected void bindEngine(CaptchaEngine engine) {
log.info("Binding CaptcheEngine: "+engine.getName());
if (defaultCapthaEngineName.equals(engine.getName())) {
defaultEngine = engine;
}
captchaEngines.put(engine.getName(), engine);
}
protected void unBindEngine(CaptchaEngine engine) {
log.info("UnBinding CaptcheEngine: "+engine.getName());
captchaEngines.remove(engine.getName());
if (defaultCapthaEngineName.equals(engine.getName())) {
defaultEngine = null;
}
}
@Activate
protected void activate(ComponentContext componentContext) {
authStorageType = PropertiesUtil.toString(componentContext.getProperties().get(PAR_STORAGE), DEFAULT_STORAGE);
defaultCapthaEngineName = PropertiesUtil.toString(componentContext.getProperties().get(PAR_CAPTHA_ENGINE), DEFAULT_CAPTCHA_ENGINE);
}
@Deactivate
protected void deactivate(ComponentContext componentContext) {
}
private CaptchaEngine getDefaultEngine() {
if (defaultEngine != null) {
return defaultEngine;
} else {
log.warn("No default engine is defined, we use the first service");
for (Map.Entry<String, CaptchaEngine> entry : captchaEngines.entrySet()) {
return entry.getValue();
}
}
return null;
}
private CaptchaEngine getCapthaEngineByName(String engineName) {
if (engineName == null) {
return getDefaultEngine();
}
if (captchaEngines.containsKey(engineName)) {
return captchaEngines.get(engineName);
} else {
log.warn("Could not found CaptchaEngine in the given name: "+engineName);
return getDefaultEngine();
}
}
private BufferedImage getCaptchaImage(CaptchaEngine engine, String captchaId, Locale locale) {
return engine.getImage(captchaId, null, locale);
}
private boolean validateCaptchaResponse(CaptchaEngine engine, String captchaId, String response) {
return engine.validateResponse(captchaId, response);
}
private boolean validateCaptchaResponse(CaptchaEngine engine, HttpServletRequest request, String response) {
return validateCaptchaResponse(engine, extractCaptchaIdFromRequest(request), response);
}
public void setCaptchaId(CaptchaEngine engine, HttpServletRequest request,
HttpServletResponse response, String captchaId) {
if (authStorageType.equals(STORAGE_COOKIE)) {
Cookie[] cookies = request.getCookies();
boolean foundCookie = false;
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals(PAR_CAPTCHA_ATTRIBUTE_NAME)) {
foundCookie = true;
cookie.setMaxAge(configurator.getSessionTimeout().intValue());
cookie.setValue(captchaId);
cookie.setPath("/");
response.addCookie(cookie);
}
}
}
if (!foundCookie) {
Cookie cookie = new Cookie(PAR_CAPTCHA_ATTRIBUTE_NAME,
captchaId);
cookie.setMaxAge(configurator.getSessionTimeout().intValue());
cookie.setPath("/");
response.addCookie(cookie);
}
} else {
if (request.getSession() == null) {
request.getSession(true).setMaxInactiveInterval(
configurator.getSessionTimeout().intValue());
request.getSession().setAttribute(PAR_STORAGE, captchaId);
captchaId = request.getSession().getId();
}
}
}
@Override
public BufferedImage getCaptchaImage(String captchaEngineName,
String captchaID, Locale locale) {
return getCaptchaImage(getCapthaEngineByName(captchaEngineName), captchaID, locale);
}
@Override
public boolean validateCaptchaResponse(String captchaEngineName,
String captchaID, String response) {
return validateCaptchaResponse(getCapthaEngineByName(captchaEngineName), captchaID, response);
}
@Override
public boolean validateCaptchaResponse(String captchaEngineName,
HttpServletRequest request, String response) {
return validateCaptchaResponse(getCapthaEngineByName(captchaEngineName), request, response);
}
@Override
public void setCaptchaId(String captchaEngineName,
HttpServletRequest request, HttpServletResponse response,
String captchaId) {
setCaptchaId(getCapthaEngineByName(captchaEngineName), request, response, captchaId);
}
@Override
public BufferedImage getCaptchaImage(String captchaID, Locale locale) {
return getCaptchaImage(getDefaultEngine(), captchaID, locale);
}
@Override
public boolean validateCapthaResponse(String captchaID, String response) {
return validateCaptchaResponse(getDefaultEngine(), captchaID, response);
}
@Override
public boolean validateCaptchaResponse(HttpServletRequest request,
String response) {
return validateCaptchaResponse(getDefaultEngine(), request, response);
}
@Override
public void setCaptchaId(HttpServletRequest request,
HttpServletResponse response, String captchaId) {
setCaptchaId(getDefaultEngine(), request, response, captchaId);
}
@Override
public String extractCaptchaIdFromRequest(HttpServletRequest request) {
String captchaId = null;
if (authStorageType.equals(STORAGE_COOKIE)) {
captchaId = UUID.randomUUID().toString();
Cookie[] cookies = request.getCookies();
for (int i = 0; i < cookies.length; i++) {
Cookie cookie = cookies[i];
if (cookie.getName().equals(PAR_CAPTCHA_ATTRIBUTE_NAME)) {
captchaId = cookie.getValue();
}
}
} else {
if (request.getSession() != null) {
captchaId = (String)request.getSession().getAttribute(PAR_CAPTCHA_ATTRIBUTE_NAME);
}
}
return captchaId;
}
}
Service bundle
Hogyan érdemes OSGi-ba szervezni
class JCaptchaBasedDefaultCaptchaEngineParameterProvider {
public static final String PROP_ENGINE_NAME = "engine.name";
public static final String DEFAULT_ENGINE_NAME = "JCaptchaDefault";
public static String getEngineName(ComponentContext context) {
return PropertiesUtil.toString(context!=null?context.getProperties().get(PROP_ENGINE_NAME):DEFAULT_ENGINE_NAME, DEFAULT_ENGINE_NAME);
}
}
@Component(label = "%jcaptchadefault.service.name", description = "%jcaptchadefault.service.description", immediate = true, metatype = true)
@Properties(value={
@Property(label="%jcaptchadefault.name", description="%jcaptchadefault.name.description", name = JCaptchaBasedDefaultCaptchaEngineParameterProvider.PROP_ENGINE_NAME, value=JCaptchaBasedDefaultCaptchaEngineParameterProvider.DEFAULT_ENGINE_NAME)
})
@Service
public class JCaptchaBasedDefaultCaptchaEngine implements CaptchaEngine {
private DefaultManageableImageCaptchaService instance;
private String name = JCaptchaBasedDefaultCaptchaEngineParameterProvider.DEFAULT_ENGINE_NAME;
@Override
public BufferedImage getImage(String id, String text, Locale locale) {
BufferedImage ret = null;
for (int i=0; i<3; i++) {
try {
ret = instance.getImageChallengeForID(id, locale);
return ret;
} catch (CaptchaServiceException e) {
}
}
return instance.getImageChallengeForID(id, locale);
}
@Override
public void init() {
instance = new DefaultManageableImageCaptchaService(
new FastHashMapCaptchaStore(), new DefaultGimpyEngine(), 180,
100000, 75000);
}
@Override
public void close() {
if (instance != null)
instance.emptyCaptchaStore();
}
@Override
public boolean validateResponse(String id, String response) {
return instance.validateResponseForID(id, response);
}
@Activate
protected void activate(ComponentContext context) {
name = JCaptchaBasedDefaultCaptchaEngineParameterProvider.getEngineName(context);
init();
}
@Deactivate
protected void deactivate() {
close();
}
@Override
public String getName() {
return name;
}
}
Service bundle
Hogyan érdemes OSGi-ba szervezni
/**
* The <code>CaptchaServlet</code> returns a captcha image and set sessionID for captcha
* servlet.
*/
@Component(label="%captcha.servlet.name", description="%captcha.servlet.description", immediate=true, metatype=true)
@Service
@Properties(value={
@Property(label="%captcha.servlet.path", description="%captcha.servlet.pathdescription", name="sling.servlet.paths", value={"/session/captcha.jpg", "/session/captcha.png"}),
@Property(name="sling.servlet.methods", value={"GET"}, propertyPrivate=true)
})
public class CaptchaServlet extends SlingAllMethodsServlet {
private static final long serialVersionUID = -2160335731233369891L;
@Reference(cardinality=ReferenceCardinality.MANDATORY_UNARY, policy=ReferencePolicy.DYNAMIC)
CaptchaService captcha;
/** default log */
private final Logger log = LoggerFactory.getLogger(CaptchaServlet.class);
Consumer bundle
Hogyan érdemes OSGi-ba szervezni
Config
org.liveSense.service.captcha.impl.CaptchaServiceImpl.cfg
captcha.storage=cookie
captcha.engine.default=JCaptchaDefault
org.liveSense.service.captcha.jcaptcha.impl.JCaptchaBasedDefaultCaptchaEngine.cfg
engine.name=JCaptchaDefault
org.liveSense.service.captcha.impl.CaptchaServiceImpl.cfg
captcha.servlet.path=/captcha/captcha.png
Kérdések?
Köszönöm a figyelmet!
Meglévő technológiák OSGi integrációja
By Róbert Csákány
Meglévő technológiák OSGi integrációja
Bemutatásra kerül, hogy nem OSGi-ra tervezett alkalmazások hogyan integrálhatóak OSGi környezetbe. Mivel nincsen rá általános recept, hogyan lehet a különböző tervezésbeli eltéréseket áthidalni, néhány hasznos gyakrolat kerül megosztásra illetve hibás minták, melyeket érdemes elkerülni.
- 780