neděle 31. ledna 2016

Spring and OSGi

OSGi

We are using OSGi framework for ensuring dynamic modularity in our solution. OSGi is great tool for this purposes. Adding and replacing bundles (let's say plugins or jars) into running system works well. Also dependecy resolving for keep all stuff in consistent state makes us happy.

Simple OSGi runtime schema

OSGi is focused on "low-level" modularity (jars, classloaders, class dependecies and their versions, import/export classes, services...). For "high-level" modularity or, better to say "business-level" features like the Inversion of Control, you need add support from outside.

Spring 

Spring is great IoC container (and even more) for creating apps. It is well known, popular and extensible. Huge community ensures support, project is very active and still inovative. Therefore would like to add Spring as IoC to our OSGi based system.

Simple Spring IoC container runtime schema

IoC and OSGi

Spring developers tried to break through OSGi world with "Spring Dynamic Modules". Unfortunately this project is almost dead. As a replacement is recommended to use an OSGi extension called "Blueprint", which is specification of IoC for OSGi framework. There are two usable implementations:
  • Eclipse Gemini Blueprint
  • Apache Aries Bluperint
... but, they are not Spring:(

Only one think which was done (by Apache ServiceMix community) is Spring framework wrapped as bundles. It was a starting point for our integration.

Our simple solution for Spring-OSGi

Our solution works very simple way which is sufficient for almost usecases:
  • Every OSGi-bundle has it's own isolated application context
  • Application context is defined by java configuration only.
  • Public and listen to service with cardinality 1..1.
  • No service filtering.
Our solution Spring-OSGi

Annotations

We need to define annotations which define spring beans as public services and also external beans which are imported via services into context (from outside).

Annotation for public spring beans into OSGi world as a OSGi service:

  @Retention(RetentionPolicy.RUNTIME)
  public @interface ExposeAsService  {}

Annotation for obtain OSGi service into spring world:

  @Retention(RetentionPolicy.RUNTIME)
  public @interface ObtainAsService {}
 
Example of application context configuration via java class:

@Configuration 
public abstract class DatabaseServicesContextConfiguration {
  @Bean
  @ExposeAsService
  protected ISystemService systemService() {
    return new SystemService();
  }
  
  @Bean
  @ObtainAsService
  public abstract IAppPropertiesService appProperties();
}

New post-processor and application-context

We need "somehow" handle new declared annotations. Therefore bean-factory-post-processor and application context will be created.

OsgiServiceObtainerBeanFactoryPostProcessor
This bean-factory-post-processor uses simple "trick": find all bean-definitions in context annotated by @ObtainAsService. Those definitions are removed and replaced by singleton instance of class which was lookuped from OSGi as service.

public class OsgiServiceObtainerBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
     private final BundleContext bundleContext;


     public
OsgiServiceObtainerBeanFactoryPostProcessor(BundleContext bundleContext) {
         this.bundleContext = bundleContext;
      }
         /** 

          * Find beanDefinitios with annotation ObtainAsService, 
          * lookup this service and register this service as

     * singletons into spring context. 
     */ 
      @Override    
      public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        
        DefaultListableBeanFactory beanFactoryEx = ((DefaultListableBeanFactory)beanFactory);

        // all beanDefinitions 
        String[] beanDefinitionNames = beanFactoryEx.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
           BeanDefinition beanDefinition = beanFactoryEx.getBeanDefinition(beanDefinitionName);
           if (beanDefinition.getSource() instanceof StandardMethodMetadata) {
              StandardMethodMetadata metadata = (StandardMethodMetadata)(beanDefinition.getSource()); 
              if (metadata.isAnnotated(ObtainAsService.class.getCanonicalName())) {            
               
               // 1. remove abstract definition 
               beanFactoryEx.removeBeanDefinition(beanDefinitionName); 
               
               // 2. get service from osgi
               String serviceClassName = metadata.getIntrospectedMethod().getReturnType().getCanonicalName();
               serviceInstance = getServiceTracker(serviceClassName).waitForService(0);
               ...   
               
               // 3. register service as singleton
               ...                
               beanFactoryEx.registerSingleton(metadata.getMethodName(), serviceInstance);
               ...
             }
         }
      }
   }
   ...
}

OsgiApplicationContext
This context is used in bundle activator and adds support for OSGi via "BundleContext" instance which is obrained in constructor. In constructor is also added "osgi bean-postprocessor" for service obtaining. Method "exposeService" ensures publicing annotated services into OSGi.

public class OsgiApplicationContext extends AnnotationConfigApplicationContext {

  private BundleContext bundleContext;

  // all exposed services.
  private List<ServiceRegistration> serviceRegistrations = new ArrayList<ServiceRegistration>();

  public OsgiApplicationContext(BundleContext bundleContext) {
    if (bundleContext!=null) {
      this.addBeanFactoryPostProcessor(new OsgiServiceObtainerBeanFactoryPostProcessor(bundleContext));
      this.bundleContext = bundleContext;
    } 
  }
  
  /**
   * Expose beans annotated as "ExposeAsService" to OSGi as services 
   */
  public void exposeServices() {
   // get beanDefinition of current bean
   for (String name : this.getBeanDefinitionNames()) {
      BeanDefinition beanDefinition = this.getBeanDefinition(name);
      if (beanDefinition.getSource() instanceof StandardMethodMetadata) {
         StandardMethodMetadata metadata = (StandardMethodMetadata)(beanDefinition.getSource()); 
         if (metadata.isAnnotated(ExposeAsService.class.getCanonicalName())) {
            String className = metadata.getIntrospectedMethod().getReturnType().getCanonicalName();
            // expose as service
            Object bean = this.getBean(name);
            LOGGER.info("Exposing bean "+name+" "+bean.toString() + " as a service "+className);
            ServiceRegistration serviceRegistration = this.bundleContext.registerService(className, bean, null);
            this.serviceRegistrations.add(serviceRegistration);
         }
      }
    }
  }
  @Override
  public void close() {
   for (ServiceRegistration registration : this.serviceRegistrations) {
      registration.unregister();
   }
   super.close();
  } 
} 


Bundle activator example

Usage of these classes is quite simple:
public class DatabaseServiceActivator implements BundleActivator {
  @Override
  public void start(final BundleContext bundleContext) throws Exception {
    springContext = new OsgiApplicationContext(bundleContext);
    springContext.setClassLoader(this.getClass().getClassLoader());
    springContext.register(DatabaseServicesContextConfiguration.class); 
    springContext.refresh();  
    springContext.exposeServices();
  } 
}

When bundle is activated, spring context will be created and obtains some beans as services from outside. After context activation (refresh call), some beans will be exposed as services. Important is to set classloader, because spring runs under it's own classloader and he cannot see your bean classes.

Future

Great news are comming from Apache Aries community! Blueprint will be implemented as "bridge" for Spring IoC. First release 0.1.0 is on download page at Apache Aries web.

When it will be more stable, we can replace our simple solution with more featured implementation.

http://aries.apache.org/modules/blueprint.html

čtvrtek 21. ledna 2016

Automobilový zázrak Magnufuel aneb podvod okem vývojáře


Jak ušetřit za palivo?

Přesně takto nazvaný sponzorovaný příspěvek na mě vykouknul na facebooku. Příspěvek byl navíc okořeněný slůvky jako "true story", "fuel-economy" a dovětkem "pokud utrácíte spousty peněz za palivo, přečtětě si tohle". V očekávání, že se dozvím něco o uspornějším způsobu řízení apod. jsem příspěvek rozkliknul.

Zde je pro zájemnce odkaz:
http://www.fuel-economy.info/CZ_Fuel-saving-blog_B8/index.php
 

Autentický blog šťastného řidiče

Odkaz me přesměroval na stránku vypadající jako blog, ve kterém se jeden taxikář chlubí, jak mu zázračné zařízení zvané "Magnufuel" ušetřilo přemnoho peněz. Článek se snaží působit věrohodně, v textu se důmyslně objevují odkazy na distributora tohoto zázraku. Na konci, jak už to bývá, je mnoho kladných odpovědí dalších řidičů.


Všimněte si použitých vět:
  • "...Prodává se v mnoha obchodech, ale nejlevnější je na český distributor..."
  • "A nakonec to nejdůležitější, tedy stránka kde si můžete koupit Magnufuel. Odkaz na stránku. Doporučuju, nebudete toho litovat. "
kde podtržená slova jsou odkazy. Ukázka z komentářů k blogu:



Při kliknutí na odkaz v článku se dostanete na stránky distributora http://www.magnufuel.com/CZ_official_T8/.

Stránky distributora

Stránky jsou interaktivní a celkem líbivé. Autor není na poli webdesignu úpný amatér. Stránka je přehledná, máme zde pěkné animace, které ukazují funkci nabízeného produktu, jsou zde dobře formulované texty, které příjemným způsobem popisují, jak vše pracuje. Tvurce použil několik technických výrazů, jako "uhlovodíkové molekuly" či "frekvence magnetické rezonance", aby čtenáři navodil dojem, že tomu hluboce rozumí. V zápětí však vše zabaluje do jednoduchých konstrukce "sraženiny", aby se dostal na úroveň laika - potenciálního zákazníka .



Celá stránka je lemována certifikacemi, čisly patentů, různých asociací a nadnárodních organizací jako General Motors. Stačí jen objednat a výsledek je zaručen:)



Necháme stranou odbornost textů a to, zda je či nění funkce výrobku fyzikálně možná, mrkněme rovnou na web jako takový z technického pohledu.

Podvodné signály

První zvláštnost, které si všimně i jen trochu zkušený uživatel, je, že počítadlo, které odpočítává 15 minut, ve kterých si máte možnost koupit si výrobek za sníženou cenu, se při každým refresh stránky restartuje. Model slev založený tímto způsobem je ekonomický nesmysl. O podikatelské levárně není pochyb.

Pár vteřin po návštěvě stránky se s pevnou pravidelností v pravém dolním rohu objevuje notifikační čtvereček s různými texty:
  • V této chvíli si tuto stránku prohlíží 172 návštěvníků.
  • Zbývá už jen 5 balíčků za akční cenu!
  • Právě byla zadána objednávka z města Opava.
Především poslední text ve mě vyvolá zvědavost a otevírám zdrojový kód stránky.

Zdrojový kód


Html kód stránky vypadá úhledně, je zarovnaný a přehledný. Autoři webu respektují SEO apod.


Neplýtvám čas podrobnějším zkoumáním, rovnou otevírám javascriptový
popup.js.php soubor. Zde nacházím vysvětlení pro otázku, odkud se beru texty v popup okně.


Ve prostřed souboru je pak vidět užití tohoto pole.


Vše se děje pomocí random čísel:) Nezapomeňte si všimnout zajímavých programátorských technik. Velice mne pobavil fragment, ve kterém se nahrazuje číslo 15 číslem náhodným.

  var text = "Zbývá už jen 15 balíčků za akční cenu!";
  var count = genRandNum(4, 8);
  catchText3 = count;
  document.getElementById("popWindow").innerHTML = text.replace("15", "<b>"+count+"</b>");


Zde mé zkoumání skončilo. Zvědavci mohou jít dále a objevit další špeky.

Podvod jak vyšitý

Věřím, že člověk s průměrnou inteligencí bez znalostí programování je schopen podvod odhalit minimálně tím, že si produkt zkusí jednoduše vygooglit, a najít tak skutečné názory jiných.

Podvedeným přeji minimum finančních ztrát a maximum poučení a vývojářům veselé pobavení.