Posts mit dem Label Google App Engine werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Google App Engine werden angezeigt. Alle Posts anzeigen

Sonntag, 17. August 2014

Tangram Release 0.9

Over the last year, Tangram has changed very much and does not look the same in all of its modules and options.

Persistence Options

The Tangram Dynamic Web Application Framework has been extended to support the Java Persistence API (JPA) and EBean as persistence layers in addition to the already available Java Data Objects solution. This greatly extends the number of options for platforms to use with Tangram.
The Tangram examples reflect this with the JDO/DataNucleus, JDO/Google App Engine, EBean, and JPA example where the later is switchable with tested API implementations for OpenJPA, EclipseLink, DataNucleus, and Hibernate.
The simple and more or less generic JDO editor now is a generic editor for all the storage solutions and built as a separate module. Except for Google App Engine this module can be left out if there is another solution available to get the content into the repository.

Dynamic Model Extenions

In addition to the Groovy implemented classes stored in the repository, which are used to extend the view and controller part of the application, you can now create JDO annotated classes which are immetiately usable as model classes in the repository. This option can only be presented for the JDO implementation.
To make the building of Tangram itself, any Tangram application, or even any application using byte-code transformation for JDO, JPA, and Ebean easier, a gradle plugin has been introduced. The examples and over blog entries illustrate the usage of this plugin.

IDE Integration

To be able to synchronize codes stored in the repository a separate FTP module has be introduced. Most IDE due to their strong PHP past support FTP better than any other protocol. CSS, JavaScript, Groovy codes can be imported and exported.

Generic Import and Export

Additionally and apart from the FTP module Tangram now contains a generic importer and exporter for the whole content stored in the repository. The XML representation can be used to transfer content between application using JPA, JDO, or EBean. IDs cannot be preserved.

Framework Independency

For the whole Tangram code the Springframework now is only an option to do the setup of the application by Spring's Dependency Injection IoC-Container and for the controller and view parts of the application.
Along this way it was easier to present a custom DI component, Dinistiq, than to use one of the available solutions. Tests have been done with Google Guice, TinyDI, and JSR-330. As of Tangram 0.9 none of these are more than experiments. Work on a Tangram JavaEE integration is also in an experimental stage and would require a major refactoring of some parts of Tangram, see other entries in this blog on the JavaEE topic.
It should now be fairly easy to do more experiments with other IoC/DI frameworks and containers or to integrate other web frameworks starting from the new plain servlet based solution.

Options

Tangram 0.9 has been tested, verified, or is even in production use on the following platforms: Google App Engine, run@CloudBees, OpenShift, and Standalone with Apache Tomcat using RDBMS or MongoDB.
The option matrix now looks like this:
Persistence: JDO, JPA, EBean
IoC: Springframework, Dinistiq
Security: Spring Security, Apache Shiro
Hosting: On Premise, OpenShift, Cloudbees, Google App Engine
Storage: RDBMS, No-SQL e.g. MongoDB, Files

Code Reduction

Tangram started as an idea to plug existing components together to form a web application with as little glue code as possible. Some of the ideas like object oriented templating though had to be coded explicitly. After this starting point the Tangram codes started growing and the 0.9 release got an additional design goal to stop this. Still some of the codes in Tangram duplicate functionality which would otherwise externally be available but avoids a depedency just to call one method. And there are still codes duplicates within Tangram where the different options e.g. for persistence might get a stronger common code base. In these areas it is still not clear if they will differentiate or grow together.

Outlook

The experiences with CapeDwarf showed the way into a Java EE module as an alternative to dinistiq, the Springframework, and the many tests with e.g. Google Guice. The not that encouraging experiences with the EBean ORM might lead to discontinued support for that module. Dinistiq will become more Java EE / CDI compatible and CDI without the full Java EE stack also seems to be an interesting option. Work on the generic import and export functions using XStream needs to be extended and easier integration of OpenID or OAuth outside of the Google App Engine is needed for many applications. Google App Engine with JPA is still on the list but not desperately needed.

Freitag, 25. Juli 2014

150 Lines just for one Collection

Some 15 years ago I left the Java Enterprise Edition train and am now pushed by things like CapeDwarf on JEE Servers to the evaluation of running Tangram somehow in a JEE environment. I read the promise, that things have become a lot easier and that JEE has adopted the annotation and convention based style I now feel familiar.
I came across the JSR330 annotations from the javax.injection package which were introduced in the JEE world. JSR330 Annotations are recognized by several Dependency Injection frameworks like Guice, Springframework, my homegrown dinistiq, and obviously the JEE Containers.

Step 1: Get rid of Spring

This is were I started to make Tangram less depending on the Springframework directly or provide interfaces to be implemented in a spring and a non-spring way. For the dependency injection part it was mostly as easy as to migrate from

    @Autowired
    private ClassRepository classRepository;


to

    @Inject
    private ClassRepository classRepository;

I lost the required = false option at that time but was able to live with that given the advantages of portability.

Step 2: Find Alternatives

The spring parts of the application where re-implemented using a plain servlet based solution, and springsecurity was replaced by Apache Shiro for those scenarios.
After having migrated to the new annotations most of my Components where portable across containers. So I don't have to sell my soul to a special DI framework for very many cases, which helps choosing the right infrastructure for every project. But there are several details which still remain problematic, most notably the missing required = false option and the missing collection of instances to be able to automatically let the whole set of instances be injected.

Step 3: Annoying Details

The annoying collection problem looks like this:

@Autowired(required = false)
private Collection<ControllerHook> controllerHooks = new HashSet<ControllerHook>();


Later I migrated this to the JSR-330 Annotations - which also works with spring.

@Inject
private Collection<ControllerHook> controllerHooks = new HashSet<ControllerHook>();


I made this work with dinistiq as well.
With Guice I learned, that it didn't like to collect the instances for me and provide me with the collection out of the box.
I'm expecting here, that any instance implementing ControllerHook from the application context gets added to a collection which then is injected into the consuming component. Spring does it, dinistiq does it, Guice can be convinced to do it by the additional Multibinder:
 
Multibinder<ControllerHook> multibinder = 
                            Multibinder.newSetBinder(binder(), ControllerHook.class);
multibinder.addBinding().to(UniqueUrlHook.class);
multibinder.addBinding().to(ProtectionHook.class);

Step 4: Show Stopper

Inspired by the CapeDwarf project, promising to be able to deploy applications developed for the Google App Engines and their APIs on a JEE infrastructure, I tried to deploy GAE based Application to such an Infrastructure based on a JBoss AS7 or Wildfly 8 Server using CapeDwarf Versions 1 or 2 respectively.
This approach doesn't work for Tangram since the used DI solution within the application - be it dinistiq or the Springframework - interferes with the JEE Container's CDI implementation. The Application Server starts to interpret the annotations intended for the other DI framework. This would instantiate the application components twice and right at the moment it also fails on some of those components.

Step 5: JEE Wordiness

So quite naturally I now went over to directly migrate Tangram to the JEE world. But also JEE has the same problem as plain Guice has.

@Inject
private Collection<ControllerHook> controllerHooks = new HashSet<ControllerHook>();

This time the solution really is ugly. It seems I have to implement a different consuming component and thus instead of the two lines, a separate ControllerHookProvider is needed

public interface ControllerHookProvider {

    Collection<ControllerHook> getControllerHooks();

} // ControllerHookProvider

Of course the generic implementation looks like the good old two lines which did the whole job for me so far.
 
public class GenericControllerHookProvider implements ControllerHookProvider {
    @Inject
    private Collection<ControllerHook> controllerHooks;


    public Collection<ControllerHook> getControllerHooks() {
        return controllerHooks;
    } // getControllerHooks()

} // GenericControllerHookProvider

For JEE an alternative implementation has to be chosen, which made the interface necessary in the first place, and it is surprisingly wordy for that common and simple scenario.

@Named("controllerHooksProvider")
@Singleton
public class JeeControllerHooksProvider implements ControllerHookProvider {

    private Collection<ControllerHook> controllerHooks = new HashSet<>();

    @Inject
    public void setControllerHooks(@Any Instance<ControllerHook> hooks) {
        for (ControllerHook hook : hooks) {
            controllerHooks.add(hook);
        } // for
    } // setControllerHooks()

    public Collection<ControllerHook> getControllerHooks() {
        return controllerHooks;
    } // getControllerHooks()

} // JeeControllerHooksProvider

This really didn't invite me to go deep into JEE again. Additionally it is missing any of the configuration file based bean definitions avoiding much of the interfacing of classes where in fact just two injected values make the difference. With the Springframework configuration files and auto scanning of beans go hand in hand. JEE intentionally left out this part.

Donnerstag, 24. Juli 2014

Fast on the first Request

It seems to be common sense with so many developers, that "expensive" things that only happen once are not that bad. And when those things happen at the wrong point in time that it is a good idea to move them to the "application startup".
So most of us are not very surprised and consider it no problem, if this startup takes some time.

Welcome to the Cloud

At a first sight, deploying my applications in the cloud is exactly the same as it used to be. I follow accepted standards, use common frameworks and respect many best practices. So any runtime platform supporting this should do the job. Cloud in that case means, that many more aspects of deployment and operations get automated - including that starting and stopping of additional instances, load balancing and so on.
But wait... When does the platform get the idea to start new instances?

Let the Customer wait?

It's just load. And load means, that Customers issued HTTP Requests and are awaiting responses in time. From research we know that in time means something around 3s. But when a user request leads to the start of a new instance to handle the load it's quite too late to do all the expensive stuff. Application startup is not the right point in time anymore.
Additionally you can learn, that all those nice precomputations simply re-calculate values the instances from yesterday or the other instance on the other machine already learned. And of course it still is a good idea to have all those values at hand - meaning to "cache" them. But very many of the values which are not coded or configured into the application deployment stay the same as long as the deployment environment doesn't change. Or they stay the same unless the application itself changes them and thus is able to tell when really to re-calculate derived values in the caches.

Examples for all this are:
  • Database derived value caches like query results with calculations based on the result, where just this application writes to the database. Those values might be needed at startup but stay constant until the application itself changes the database contents.
  • Classpath scanning to auto-discover software components. The developers and deployers wont want to collect the lists manually or at build time but the components don't change after the deployment. And definitely not on every startup.
  • Webtemplates and other codes which need to be fetched and prepared for use (e.g. get compiled). Those codes get prepared on every startup of the application while they change not that frequently and especially not on application startup. Obviously they add a big amount to the the response time since those codes need to be executed for the generation of the response.

Just use a Cache

What? Oh, well not that Cache. This Cache. This cache doesn't need to be as fast as the in memory caches already available and I didn't want to re-invent the wheel. They just need to have some values at hand some other instance already calculated and they have to be changed when these values change - which is - as already pointed out - not as frequent as other runtime values.
The values in the cache still are volatile and can be re-calculated by the application at any time. But they should be persisted for some time to be available at startup and reduce the time for the first request in web applications.
The jsr107 cache implementation in the Google App Engine is an example of exactly this approach. I wrapped this cache as a PersistentRestartCache in Tangram and cut reponse times for the first request to a third (or half as the worst case scenario) from around 30s to 11s. For all other platforms available for the Tangram dynamic webapplication framework I presented a simple (maybe too simple) file based implementation which does most of the job as well.
Still this leaves out the classpath scanning of the Springframework or dinistiq. In my environment the Springframework uses between 3s and 7s and dinistiq around 4s to do the basic application setup based on classpath scanning and additional configuration files. So half of the time the startup deals with the framework code and not the applicaiton startup itself. And this value only was achieved by nearly brutal reduction of the portion of the classpath to be scanned creating a "components" package where all autoscanned components reside for the Springframework and dinistiq.

Resume

Caches are a good Idea. Applications with a dynamic deployment are a good idea. Thinking of the application startup as a point in time where long calculations might take place is not that much of a good idea (at least anymore) where instances should be automatically braught up depending on load and not human decisions.
So simply bring together caches with persistence and knowledge when those caches can be invalidated. As an example I did this for the tangram web application framework and had quite some success on the Google App Engine, run@cloudbees, and OpenShift cloud platforms for the Java world.
The last but also important point: Optimizing the application startup in general is worth the time nowadays.

Donnerstag, 17. Juli 2014

Not that Groovy

Ist es eigentlich schon allgemein akzeptiert, daß Web Anwendungen nicht mehr davon ausgehen dürfen, daß es für sie eine entspannte Startphase gibt? Daß sie auch beim ersten Request für den Endbenutzer ausreichend schnell antworten sollten? Es klingt immer noch häufig so, als wären "teure" Dinge, die nur einmal in der Anwendung passieren kein so großes Problem und das man dieses "teure" einfach auf die Startphase verschiebt.
Alles was ich hier schreibe, ist nur für Projekte relevant, in denen ein Deployment nennenswerte Kosten verursacht oder es aus anderen Gründen keine gute Idee ist, ständig die gesamte Software installieren. Mir war diese Stabilität mit großen Systemen auf Basis CoreMedia immer ein so großer Vorteil, daß ich das auch mit Tangram im kleinen nachgezeichnet habe (bzw. dort sogar für die großen Projekte vorgezeichnet habe). Dennoch muß man auf wechselnde Anforderungen genauso dynamisch reagieren, wie auf die Anfragen der Kunden an das System.

Messungen mit Groovy-Code in der Datenbank

Um die Software einfacher und schneller an Anforderungen anzupassen, habe ich mich ja vor Jahren auf Groovy-Code in einem irgendwie gearteten Repository festgelegt. Wir kennen das von Stylesheets, JavaScripts, Templates (z.B. Freemarker, Apache Velocity).
Damals habe ich für einen großen deutschen Telekommunikationskonzern eine Reihe von Messungen durchgeführt, die deutlich belegt haben, daß der Zugriff auf eine fertig kompilierte Groovy-Klasse - oder gar eine vorher abgelegte Instanz davon - keine Nachteile gegenüber dem Java-Code des statischen Programmteiles hat.
Für den einzelnen Request einer Webanwendung ist es also vollkommen egal, ob der Code nun deployed wurde oder in der Datenbank liegt.
In der Folge hat die typische Tangram Webanwendung kaum noch Anteile an Java-Code (siehe z.B. amor oder dragon-map usw). Es wird alles über Templates mit Apache Velocity für die Darstellungsschicht und Groovy-Code für die Geschäftslogik oberhalb der Datespeicherung bis hin zur URL-Struktur erledigt. (Mit JDO kann sogar ein Teil des Datenmodells so in den dynamisch veränderbaren Teil verlagert werden.)
Die Effizienz bei der Ausführung wird durch einfaches Kompilieren und Instanziieren beim Speichern der Codes erreicht.
Und hier genau schlummert ein Problem, das sich mir nun richtig stellt, wo einiges an "dynamischen" Codes (also solchen in der Datenbank, die potenziell häufiger geändert werden) zusammengekommen ist:
Im Gegensatz zu den Aufrufen der Anwendung und dem direkten Benutzen der Instanzen erhalten wir beim Start des Systems einen deutlichen Performance-Nachteil, da hier nun einiges an Datenbankabfragen und Kompiliervorgängen sowie Instanziierungen zusammenkommt. In der Summe ist das deutlich aufwendiger als die entsprechenden Vorgänge mit statischem Java-Code.
Warum interessiert der Start aber nun gerade wieder? In den meisten Szenarien kommt so etwas doch nicht gerade häufig vor.

Willkommen in der Wolke

Für einen selbstbetreuten "on premise" Servlet/JSP Container mag das stimmen, aber gerade für die "billigen" Modelle in der Wolke und z.B. auch die Google App Engine, OpenShift oder run@cloudbees stimmt das überhaupt nicht, da hier zum Skalieren und bei Nichtbenutzung der Trick gerade darin besteht, Instanzen der Anwendungen herunterzufahren oder neue hinzuzustellen.
Bei Instanzen auf der Google App Engine, bei denen keine sonstigen Zusicherungen eingestellt sind, führt das zu einem netten Hinauf- und Herunterfahren wie ein Jojo. Bis Version 0.7 beinhalten alle Tangram Anwendungen einen Cron-Job, der genau das verhindern sollte, indem die Anwendung sich selbst aufgerufen hat. Vor einiger Zeit habe ich mich dem Problem nun endlich gestellt und versucht, die Startup-Sequenz einmal zu tunen. In der Ausgangslage sind wir bei einem Erst-Start im Bereich von gerne 30s abgelaufene Zeit.

Spring Tuning

Das ist nicht das erste Mal: Bereits früher hat das Springframework gezeigt, daß es den Komfort für den Entwickler mit "Kosten" beim Start belegt. Der Scan der Klassen nach Annotationen und Komponenten sowie deren vollautomatisches Zusammensetzen dauert auf einem mittelprächtigen Rechner zwei bis drei Sekunden, in der App-Engine jedoch gerne bis zu acht Sekunden, die ein Kunde am Browser warten müßte. Das konnte damals durch das Eingrenzen beim Scan reduziert werden:

    <context:component-scan base-package="org.tangram" />

Die Angabe des Base-Package ist hier wichtig und grenzt den Bereich der Klassen, die geprüft werden, nachhaltig ein, was einige Sekunden bringt. In Zukünftigen Versionen sollten hier noch weitere Einschränkungen greifen, weil das "base package" org.tangram immer größer wird und über eine Anzahl von Jars verteilt ist. Ab Version 0.8 müssen daher all Komponenten, die gerne gescannt werden möchten "org.tangram.components" mit Vornamen - also Package-Namen - heißen.

    <context:component-scan base-package="org.tangram.components" />

Dazu wurden einige Klassen in den Paketen verschoben und es bringt wieder einmal ein paar Bruchteile von Sekunden ein. Lohnend, aber es durchbricht die fachlich Sortierung der Klassen ein wenig. (Aus org.tangram.jdo wird org.tangram.components.jdo - man findet sich also schon zurecht)

Caching

Moment! Wieso Caching? Das Füllen der Caches - soweit zu dem Zeitpunkt hilfreich oder notwendig  - ist doch gerade das, was den Start u.a. so langsam macht. Aber die Inhalte der Caches werden dann bei Start genau dieselben sein, wie beim letzten Herunterfahren - oder dieselben, die schon eine andere Instanz berechnet hat. Es wäre also ganz nett, wenn man einfach auf diese Wert zurückgreifen könnte.
Das tut Tangram letztlich auch bereits seit langer Zeit in der Google App Engine: Der Scan nach Klassen, die zur Persistenz-Schicht gehören, fällt in die gleiche Rubrik wie der Scan des Spring-Framework und die Daten, die sich die BeanFactory hier merken muß, werden in der Google App Engine im Memory Cache über die JSR107 Schnittstelle gespeichert.
Diese Schnittstelle soll uns nun helfen, noch mehr Vorgänge nur so häufig auszuführen, wie es notwendig ist, unabhängig davon, ob es wir von einem Start-Request sprechen oder mitten in der Arbeit sind. Das macht die Webanwendungen Wolken-tauglicher als sie es bisher sind.
Die Benutzung dieses Caches ist denkbar einfach:

try {
  CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory();
  jsrCache = cacheFactory.createCache(Collections.emptyMap());
} catch (CacheException ce) {
  log.error("()", ce);
} // try

Und danach ist es mit put() und get() getan, wobei man natürlich immer gewahr sein muß, daß einmal nichts im Cache ist. - Und einige kompliziertere Sache - obwohl java.lang.Serializable markiert - kann man zwar hineinstopfen, findet sie in der Admin-Oberfläche, bekommt sie aber nicht mehr heraus.
Das gilt leider insbesondere für per JDO persistierbare Objekte und kompilierte Java Klassen.

Tunen wo es wehtut

Langsam ist bei einem leeren Cache das beziehen aller Codes, die für die Website notwendig sind, aus dem Datastore. Hier geht es schon bei wenigen Codes eher um Sekunden als die oben zitierten Bruchteile. Also enthält Tangram ab Version 0.8 einen Simplen, persistenten Query-Cache, der genau wie der transiente Cache die IDs der Objekte der Ergebnislisten den Queries erfaßt und ablegt. Dieser Cache bringt der gesamten Anwendung bei ersten Aufrufen einer Instanz eine deutlichen Schub bei abfragebasierten Anwendungen.
Mit diesen ID-Listen muß man sich leider immer noch an den Datastore wenden, um die Objekte zu beziehen, da diese Objekte ja nicht in den Cache gelegt werden konnten.
Diesen Schritt aber wiederum kann man sich bei den Codes gut sparen, in dem man sie beim Lesen in reine transiente Objekte umwandelt (hey, es sind Codes!) und sie dann im persistenten Startup Cache ablegegen zu können.
Jetzt ist eine der längsten Phasen beim Starten das Kompilieren der Groovy-Klassen. Hier den Binärcode zu cachen hat sich bisher als nicht umsetzbarer Ansatz erwiesen. Aber evtl. kommt das ja auch noch. So ist der limitierende Faktor beim Startup auf die Anzahl der Groovy-Codes und den Compiler gesetzt. Mehr ging bisher nicht, aber es hat den Startup in der Zeit gedrittelt. - Und die Ansätze daraus sind verallgemeinerbar für Anwendungen in der Wolke.

Dienstag, 15. Juli 2014

Google App Annoyance - aka Engine

Together with the missing support for the Servlet 3.0 specification in Google App Engine (and, yes, there are still too many situations were this specification level ist not available) we are reading from Google for their App Engine that classpath scanning is an issue on that platform and that this is one of the reasons that kept them from supporting this version of the servlet specification.
What I didn't read from Google is, that they noticed that the Servlet 3.0 version is (arguably) one of the most important steps since Java came to the web. There is no major framework in the Java world anymore not doing any classpath scanning right now. The mentioned Springframework I'm using is just one example.
After some five years of work with the Google App Engine and Java as the only language in use, I changed my mind and don't consider the App Engine as one of the first choices in cloud platforms for the Java world anymore.
And this is really just because of these two small issues.
Like very many others I'm using the Springframework - with component scan and thus with classpath scanning. Any first request to an instance take ages. But "first requests" are common in the cloud, where instances need to be shut down and brought up depending on load. And the cloud is what Google App Engine is about, isn't it?
I invested quite some effort to learn how to be fast on the first request while still using the Springframework and even developed my own stripped down, minimalistic Dependency Injection environment for the application setup (dinistiq) to only have the features I'm using at hand. But this all didn't help to make the end user experience satisfying. Things feel slow.
So in the end this all gave the push for Tangram to support that many new platforms, use the CI Features and Repositories at cloudbees, enjoy the command line access of OpenShift. This brought options of different frameworks and I learned much about cloud deployment and operation scenarios. So in that respect we should be thankful for the Google App Engine weakness.
But it still is the source of some level of complexity and number of artifacts flying around in what I call my dynamic web application framework Tangram. Also this currently renders Google App Engine the second best cloud platform for Java while still having great web based monitoring tools.

Sonntag, 10. November 2013

Splitter im Frühling

(English summary at the end)
Am Ende dieses Beitrags kommt eine universeller Konfigurations-Helper für das Springframework heraus.
org/tangram/spring/PropertySplittingPlaceholderConfigurer.java
Aber warum man so etwas brauchen könnte, wollte ich kurz an zwei oder vier (je nachdem wie man es zählen möchte) zeigen.
Bisher habe ich das Springframework und die jeweiligen Persistenzschicht immer komplett unabhängig voneinander genutzt: Java Persistence API (JPA) konfiguriert man über eine persistence.xml und Java Data Objects (JDO) über eine jdoconfig.xml. ORM Integrationen habe ich auch im zusammenspiel mit Spring MVC nicht benötigt. - Dachte ich.
Aber insbesondere durch Cloud-Umgebungen habe ich nun lernen müssen, daß der Weg über diese Dateien eigentlich nicht gerade "best practice" ist und eher für einfache Situationen taugt.
Wenn man dann endlich neben der Nutzung der Google App Engine auch mal einen Ausflug nach Cloudbees und OpenShift macht, tritt nämlich ein kleines Problem zutage: Die Werte in den oben genannten Dateien können nicht, wie alles andere, das ich in Spring "zusammenstecke", mit Platzhalter versehen werden, die erst zur Laufzeit des Systems aufgelöst werden.
Durch diese Ersetzung, wie sie z.B. Bei Spring quasi automatisch passiert - paßt sich ein ein grundsätzlich vorkonfiguriertes System dann in seine Laufzeitumgebung ein. - Bis auf die Persistenzschicht in meinem Fall.
Als einfachstes Beispiel nehmen wir mal die Verbindungsdaten zu einer Datenbank unter OpenShift. Diese sollte man am sinnvollsten aus den Umgebungsvariablen lesen, sagt die "best practice" von OpenShift.

JDO auf OpenShift

Also nimmt man beim Einsatz von JDO die Werte

<persistence-manager-factory name="transactions-optional">
  <property name="javax.jdo.PersistenceManagerFactoryClass"

            value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>     
  <property name="javax.jdo.option.ConnectionURL"

            value="mongodb://localhost:8111/db"/>
  <property name="javax.jdo.option.ConnectionUserName" value="u"/>
  <property name="javax.jdo.option.ConnectionPassword" value="p"/>
</persistence-manager-factory>


aus der jdoconfig.xml komplett heraus und übergibt sie bei der Instanziierung der PersistenceManagerFactory mit:

factory = 
JDOHelper.getPersistenceManagerFactory(jdoConfigOverrides, 
                                       "transactions-optional");

und diese jdoConfigOverrides bezieht man dann aus der Spring-Configuration, wo sie von den Ersetzungen auf Basis von Umgebungswerten profitieren:

<bean id="jdoConfigOverrides" class="java.util.HashMap">
  <constructor-arg>
    <map>
      <entry key="javax.jdo.option.ConnectionURL"

      value="mongodb://${OPENSHIFT_MONGODB_DB_HOST}:${OPENSHIFT_MONGODB_DB_PORT}/test"/>
      <entry key="javax.jdo.option.ConnectionUserName"

             value="${OPENSHIFT_MONGODB_DB_USERNAME}"/>
      <entry key="javax.jdo.option.ConnectionPassword"

             value="${OPENSHIFT_MONGODB_DB_PASSWORD}"/>
    </map>
  </constructor-arg>
</bean>

JPA auf OpenShift

Entsprechend geht man bei JPA vor und nimmt die Werte

<persistence-unit name="openjpa" transaction-type="RESOURCE_LOCAL">
  <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
  <exclude-unlisted-classes>false</exclude-unlisted-classes>
  <properties>
    <property name="javax.persistence.jdbc.url"

              value="jdbc:postgresql://localhost:5432/postgres"/>
    <property name="javax.persistence.jdbc.user" value="un"/>
    <property name="javax.persistence.jdbc.password" value="pw"/>
  </properties>
</persistence-unit>

auch hier aus der Konfigurationsdatei heraus und fügt sie in die Spring-Konfiguration ein:

<bean id="jpaConfigOverrides" class="java.util.HashMap">
  <constructor-arg>
    <map>
      <entry key="
javax.persistence.jdbc.url"
  value="jdbc:postgres://${OPENSHIFT_POSTGRESQL_DB_HOST}:${OPENSHIFT_POSTGRESQL_DB_PORT}/db"/>
      <entry key="
javax.persistence.jdbc.user"
             value="${OPENSHIFT_POSTGRESQL_DB_USERNAME}"/>
      <entry key="
javax.persistence.jdbc.password"
             value="${OPENSHIFT_POSTGRESQL_DB_PASSWORD}"/>
    </map>
  </constructor-arg>
</bean>

denn auch hier gibt es entsprechende Parameter beim Erzeugen in diesem Fall der EntityManagerFactory:

factory = 
Persistence.createEntityManagerFactory(persistenceUnitName,
                                       jpaConfigOverrides);

Cloudbees

Über die Vorgehensweise hier stolperte ich erst, als ich meine Anwendungen mit Tangram auf OpenShift betreiben wollte, nachdem sie bei cloudbees schon liefen, da es für MySQL auf run@cloudbees eine "managed" Lösung mit einer DataSource gibt

 <!-- jndi datasource example (run@cloudbees) -->
 <property name="datanucleus.ConnectionFactoryName" 

           value="java:comp/env/jdbc/mydb" />

Das Problem besteht also mit der "hauseigenen" MySQL Datenbank dort überhaupt nicht.
Aber auch in dieser Umgebung werden ganz allgemein Werte der Betriebsumgebung an die Anwendungen durchgereicht und sollten von dieser auch benutzt werden.

URL Splitting in der Spring-Konfiguration

Das geht leider nicht ganz genau wie oben beschrieben, da unter Cloudbees z.B. für MongoDB die Verbindungdaten in einer URL übergeben werden (die gibt es auf OpenShift auch, aber man kann dort auch direkt auf die Einzelteile zurückgreifen).
Die Lösung ist hier mit ein wenig Programmieraufwand (s.o.) verbunden, da ich mich entschlossen habe, die Property-Ersetzungen durch Spring an dieser Stelle ein wenig aufzubohren und ganz allgemein URLs in ihren Teilen nutzbar zu machen.
Den

<bean id="propertyConfigurer" 
  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="locations">
    <list>
      <value>classpath*:/tangram/*.properties</value>
      <value>/WEB-INF/tangram/*.properties</value>
    </list>
  </property>
</bean>

ersetze ich also durch einen eigenen

<bean id="propertyConfigurer" 
      class="org.tangram.spring.PropertySplittingPlaceholderConfigurer">
  <property name="locations">
    <list>
      <value>classpath*:/tangram/*.properties</value>
      <value>/WEB-INF/tangram/*.properties</value>
    </list>
  </property>
</bean>

Danach steht von jeder URL, wie z.B. ${MONGOHQ_URL_MYBD} für den Service MongoHQ auf Cloudbees mit der verbundenen Datenbank MYDB, die Teile zur Verfügung:

<bean id="jdoConfigOverrides" class="java.util.HashMap">
  <constructor-arg>
    <map>
      <entry key="javax.jdo.option.ConnectionURL"

value="mongodb:${MONGOHQ_URL_TANGRAM.host}:${MONGOHQ_URL_MYBD.port}/${MONGOHQ_URL_MYDB.uri}" />
      <entry key="javax.jdo.option.ConnectionUserName" 

             value="${MONGOHQ_URL_MYDB.username}" />
      <entry key="javax.jdo.option.ConnectionPassword" 

             value="${MONGOHQ_URL_MYBD.password}" />
    </map>
  </constructor-arg>
</bean>

Das macht die Implementierung für alles, was sie für eine URL hält, und damit wird sie zu einem recht universellen Werkzeug bei der Spring-Konfiguration.
Jede URL in einer Property-Datei

# Example
url=mongodb://ruth:guessme@mongo.host:8111/db

wird zerlegt in

url.username=ruth
url.password=guessme
url.host=mongo.host
url.port=8111
url.uri=db 

Diese Platzhalter fügt der PlaceholderConfigurer dann an allen gewünschten Stellen ein. Das sollte für einen ganzen Bereich von Anwendungen erst einmal mit einem Werkzeug ausreichen.

English (sort of) Summary

For easier parameter passing through the springframework down into the JDO or JPA persistence layers of you app - especially on the plattforms of CloudBees and OpenShift - I introduced a PropertySplittingPlaceholderConfigurer, which splits everything it consideres a URL into the parts host, port, username, password, protocol, and uri.
So anything in the form of

# Example
url=mongodb://ruth:guessme@mongo.host:8111/db

gets exploded as if it would read

url.protocol=mongo
url.username=ruth
url.password=guessme
url.host=mongo.host
url.port=8111
url.uri=db

These generated properties can subsequently be used as placeholders in your springframework XML configuration files. So, what started as a little helper to connect to the databases of OpenShift in the best practice way, ended as a small but universal helper class.

Sonntag, 29. September 2013

Tangram 0.8 Release - Mehr Dynamik bitte

Es wurde langsam Zeit für ein neues Tag auf dem Tangram Repository. Die Änderungen gegenüber der letzten Version sind doch umfangreich und viele Anwendungen, die Tangram nutzen - und dringend Version 0.8 brauchen, sollten wieder eine Stabile Basis bekommen.
Die aktuelle Fassung auch von den Beispielen gibt es als Code wie immer bei github und die Dependencies bezieht man aus dem amor.

Was gibt's neues?

Version 0.8 beschäftigt sich viel mit Dynamik. Dazu bedurfte es eines Erheblichen Tunings - insbesondere auf der Google App Engine - allgemein für die Nutzung in der Cloud. Aber etwas anderes kommt unter der Überschrift Dynamik ja auch nicht in Frage.
Zu den umfangreicheren Caching Techniken ebenfalls mit Zielgebiet Cloud werde ich hier noch einen separaten Beitrag veröffentlichen.
Mit Version 0.8 ist die Programmierung von Tangram-Anwendungen nun endgültig in die Datenbank gewandert und wird dort mit Apache Velocity und Groovy umgesetzt, wobei einige Schwächen und Fehler bereinigt wurden.
Dabei sind nun neben den bekannten URLs auch Benutzeraktionen auf der Weboberfläche mit (in den sogenannten Shims) Groovy umsetzbar.
Das dynamische Zusammenstellen der Inhalte auf der Site ist nun ausreichend performant, in der API vollständig und ebenfalls mit Groovy handhabbar.
Um das alles benutzbar zu halten wurde der Editor - eigentlich nur ein Stiefkind in Tangram - in wichtigen Details verbessert und in den Abläufen einfacher gestaltet.
Technisch wurde die RDBMS Umsetzung vom Proof of Concept neben der Google App Engine zu der Hauptumsetzung und das System um die Anbindung MongoDB erweitert. Dabei wurde der JDO-Layer von den historischen Altlasten früher Google App Engine Zeiten befreit und auf API-Level 3.0 gehoben.
Natürlich sind alle genutzen Komponenten auf aktuelle Versionen umgestellt und auch das Buildsystem mit Gradle - jetzt in Version 1.8 - konsistent weiterentwickelt.

Neuer Standard-Startpunkt

Wer nun eine neue Web-Idee ausprobieren möchte und kein Geld in die Hand nehmen will, nimmt nicht mehr die Tangram und die Google App Engine und den Google Apps sondern Tangram und Cloudbees - ggf. mit MongoDB als Backend, an dieser Stelle sogar mit integrieten git-SCM und Continous-Integration-Server über Jenkins.
Auch hier gilt: Wenn die Idee fliegt, besteht ein professionelles Angebot für eine kommerzielle Nutzung der Plattform.
Ersatz für die Google Apps, die wir in der Vergangenheit für den Rest des Auftrittes wie Mails, Calender, Dokumentablage etc. genutzt haben, findet man bei zoho.

Mittwoch, 18. September 2013

NATURINSPIRIERT.org mit Tangram

Die Meldung kommt eigentlich etwas spät, aber es gibt einmal wieder eine neue Site, die mit Tangram realisiert wurde. Das kleine Foto-Blog naturinspiriert.org ist mit dem Tangram Framework auf der Google App Engine realisiert worden.
Das ganze ist insofern technisch dennoch nicht "yet another" und "nur klein", da hier zum ersten Mal live die dynamsichen Content-Zusammenstellungen genutzt werden. Die einzelnen Rubriken der Site befüllen sich mit den passenden Artikeln von selbst. Alle anderen Projekte, die diese Feautures von Tangram 0.8 nutzen sind leider noch nicht online oder können hier nicht öffentlich erwähnt werden.
Was bei der Umsetzung aufstieß sind allerdings die Probleme mit der Google App Engine: Man darf sich nicht mehr blenden lassen von veralteten Hinweisen und Links: Ohne kostenpflichtigen Google Apps Zugang bekommt man keine eigene Domain mehr für Google App Engine Anwendungen geschaltet. Punkt. Die erwähnten Links gibt es nicht mehr. Wir haben sie in keinem Szenario aus Google App Engine Anwendungen und Google Accounts nachstellen können.Wir werden also nun weiter an den alternativen Arbeiten.
Wenn's Geld kosten darf, ist Google Apps sicherlich eine gute Lösung und Google Appengine eine sehr attraktive Plattoform. Wenn man allerdings noch nicht weiß, ob eine Lösung Geld verdient, ist es nach erfolgreichen Jahren seit 2013 nur noch zweite Wahl.

Mittwoch, 9. Januar 2013

JDO 3.0 in der Google App Engine

Es ist nicht gerade ganz neu, aber ich habe es nun einmal bis jetzt aufgeschoben, meine Google App Engine nutzenden Anwendungen auf JDO 3 zu heben, obwohl das ja nun angeblich möglich ist. 
Das wichtigste dabei wären die "unowned relationsships" (was ein Wort-Ungetüm), da sie der Grund sind, daß wir bisher keine referenzielle Integrität auf Datenebene haben und diese merkwürdigen Umwege über die IDs als Strings in in den Datenfeldern nutzen müssen. In Tangram gibt es da einen Haufen Methoden (getIds, getContent(), getContents()), die einem das Leben einfach machen, aber eigentlich sollte das alle ersatzlos gestrichen werden.
Google beschreibt die Umstellung als manuellen Weg, die Anwendung zu modifizieren.
An andere Stelle kursieren immer wieder Anleitetungen, wie man das mit dem Eclipse Plugin für die Google App Engine bewerkstelligen soll. Nur genau dieses Plugin nutze ich natürlich in einem Projekt nicht, das ich auch anderen Menschen zur Verfügung stellen will, da dort mein Anspruch ist, daß man einfach ein Build-Systam hat, das alles genauso zusammenbaut wie ich bei mir.
Mit einer IDE habe ich so etwas noch nie erreicht, also bleibe ich nach make, ant, maven nun bei gradle und will hier einmal für mich und andere Aufzählen, was man zu Umstellung machen muß:
Schritt eins ist, die Abhängigkeit der JDO API zu ändern. In Gradle-Notation wird
javax.jdo:jdo2-api:2.3-eb zu
javax.jdo:jdo2-api:3.0.1
(Die letzten Maven-Nutzer werde das auch lesen können)
Und dann brauch noch die jdoconfig.xml ein paar kleinere Anpassungen:
<property name="javax.jdo.PersistenceManagerFactoryClass"
          value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
wird zu
<property name="javax.jdo.PersistenceManagerFactoryClass"
          value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>

Außerdem kommen noch zwei kleine Zeile dazu
<property name="datanucleus.appengine.query.inMemoryWhenUnsupported" value="true"/>
<property name="datanucleus.appengine.relationDefault" value="unowned"/>
Die obere Zeile ergibt sich aus den geänderten Paketen für die aktuelle datanucleus Version und die beiden anderen Zeilen versuchen, die performance zu erhöhen und die "unowned relationsships" als default zu setzen, wie es auch bei allen anderen Stores außer der Appengine der Falle ist.
Wenn man das getan hat, funktioniert bis auf die paar Semantik-Änderungen alles wie gehabt und man kann beginnen die Model-Klassen umzuschreiben.
Google beschreibt, daß auch noch die JPA-Anbindung in datanucleus hinzugefügt werden sollte, aber die nutzen ich nicht und so entsteht die Abhängigkeit nicht. Desweitere fehlt noch geronimo-jpa_2.0_spec-1.0, was aber für JDO wieder keine Rolle spielen sollte..
Aus 
package org.tangram.gae.solution;
 
import java.util.List;

import org.tangram.jdo.JdoContent

import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable
public class Container extends JdoContent {

    private List<String> contentIds;


    public List<Topic> getContents() {
        return getContents(Topic.class, contentIds);
    }


    public void setContents(List<Topic> contents) {
        contentIds = getIds(contents);
    }

} // Container
wird dann das deutlich lesbarere
package org.tangram.rdbms.solution;

import java.util.List;

import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable
public class Container extends Linkable {

    @Unowned
    private List<Topic> contents;


    public List<Topic> getContents() {
        return contents;
    }


    public void setContents(List<Topic> contents) {
        this.contents = contents;
    }

} // Container

wie wir es schon in der rdbms Variante nutzen. Endlich...
(Daß ich dabei Probleme in der rdbms-Variante entdecken mußte steht auf einem anderen Blatt und damit in einem späteres Posting ;-) )

Samstag, 22. Dezember 2012

Das war nicht gut

Neulich habe ich hier mal einen Workaround mit Gradle für das Classpath-Problem mit dem YUI-Compressor beschrieben.
Dieser Workaround funktioniert solange sehr gut, wie man in den Web-Archiv-Artefakten alle Java-Bibliotheken mitschleppt und dann beim Kopieren daraus auswählen und umbenennen kann.
Genau das habe ich gestern mit der Version 0.7 von Tangram aber endlich eingedämmt, da man jede Menge Code und damit Daten ständig von A nach B schiebt. Man kann eigentlich all die JAR-Dateien einfach weglassen. Sie kommen automatisch durch die Abhängigkeiten der restlichen Codes wieder rein. Alle JARs? Ein kleines, problematisches Archiv leider nicht: der YUI-Compressor. - Also habe ich die Mimik mal wieder umgebaut.
Dazu basteln wir uns eine weitere Configuration, wie das für die Google App Engine schon für das enhancen der JDO-Klassen notwendig war:

configurations {  .
  .
  .
  yui
}

Mit dieser Konfiguration belegen wir nur den YUI-Compressor:

dependencies {
  .
  .
  .
 providedCompile "com.yahoo.platform.yui:yuicompressor:$yui_version"
 yui "com.yahoo.platform.yui:yuicompressor:$yui_version"
  .
  .
  .
}

Er wird also aus den Abhängigkeiten entfernt und nur in der Konfiguration genutzt. Und die Elemente dieser Konfiguration können wir nun wieder beim Kopieren umbenennen, wen man das abschließende into('') etwas tuned.

war.doFirst {
  .
  .
  .
  for (f in configurations.yui.files) {
    copy {
      from f.absolutePath
      into "$buildDir/target/WEB-INF/lib"
      rename 'yui', 'aaa-yui'
    }
  }
 
  into ('') {
    from "$buildDir/target"
    exclude 'WEB-INF/lib/tangram-**'
  }
}
Und damit wäre dieser Bereich auch wieder heil und kann wieder als Vorlage für entsprechende Fälle mit dem YUI-Compressor und Gradle dienen.

Freitag, 21. Dezember 2012

Artefakte mit viel Liebe

Es liegt ganz sicherlich an mir, daß ich zwar nun für fast alles, was man so zu Entwicklung und Betrieb braucht, dienste in der "Cloud" gefunden habe, aber das Bereitstellen von Bibliotheken via Maven Repository noch nicht hingebekommen habe.
Aber im Grunde - so von der lesenden Seite - sind das ja nur Verzeichnisstrukturen mit einigen festgtelegten Dateien darin, die man per HTTP zugreifen kann. - Und das solle ist: Alle Daten dazu habe ich ja schon lokal auf der Platte, wenn ich meinen Build - bei mir ja immer mit Gradle - abgeschlossen habe.
Bis ich also verstanden habe, wie ich meine Projekte in den Prozeß in die Zentralen Maven Repositories einkippe, habe ich eher mal eben heute für die Zeit bis dahin ein Ad-hoc Maven Object Repository mit dem Tangram - in Version für die Google App Engine - geschrieben und es deshalb amor genannt.
Was mußte man dafür tun? Es gibt nur ein Datenmodell: Das Artefakt, wie es im Repository zugänglich gemacht werden soll. Der Rest ist URL-Format und eine kleine Listing-Seite - und damit sind das nur noch Dinge, die man dynamisch im Tangram mal schnell hinzufügen kann. Der Code findet sich auf github - mit Datenbank Abzug diesmal.
Die Beispiel-Site ist nun das Repository auf dem ich das aktuelle Tangram 0.7 zur Verfügung stelle: http://my-amor.appspot.com/

Freitag, 14. Dezember 2012

My App Engine

Der Schock am Wochenende, was sich bei Google mit dem Gesamtszenario aus Google App Engine und Google Apps, um eigenen Domains der App-Engine Anwendung zuordnen zu können, so verändert, ist ja wieder immer heilsam. Panik ist sicherlich übertrieben und die spontane Sicht etwas pointiert, aber es ist nicht der erste Schritt, bei dem Google Einschnitte im Bereich App Engine und Google Apps vornimmt. Und so war es wieder einmal Zeit, sich die Optionen anzusehen. 
Mit Tangram steht man nicht gerade festgelegt da und kann die Anwendungen normalerweise direkt auf einen Servlet/JSP Container umstellen und sich eine Datenbank suche, die von DataNucleus' JDO Implementierung unterstützt wird.
Das ist natürlich mit einer Umstellung der Codes verbunden. Außerdem hat sich ja gerade die Frage nach der Datenmigration als einer der verschlechterten Punkte in der Google App Engine herausgestellt. Es ist also eine gute Option für Tangram aber eventuell eine schlechte für ein bereits damit bestehenden Projekt.
Beim Stöbern im Netz stößt man als zusätzliche Option mitterweile aber auch auf zwei Projekte, die es sich zum Ziel gesetzt haben, eine zur Google App Engine kompatible Infrastruktur auf anderen Systemen als denen von Google zum Laufen zu bekommen.
AppScale und TyphoonAE wollen sich dabei wie ein "Plug-In-Replacement" anfühlen, sodaß die Anwendungen ohne Änderungen laufen können. Dafür muß man dann eben mit unterschiedlichen Mitteln - entweder entsprechenden Computern oder Cloud-Infrstrukturen - die App Engine Infrastruktur nachstellen - also statt an der Anwendung gewaltig and er Umbgebung arbeiten.
Um es gleich ganz deutlich zu sagen: Für keine der beiden Umgebungen sind alle APIs umgesetzt und keine ist in einem Zustand, daß man über einen produktiven Einsatz wirklich nachdenken sollte. Es ist nur jetzt an der Zeit, sich prototypisch anzusehen, ob man mit dem Weg etwas anfangen kann, und ihn mitzugehen oder nicht.
Die wesentliche Warnung an dieser Stelle ist aber eine, die bleiben wird: Die APIs und Lösungen der Google App Engine müssen bei beiden Lösungen nachprogrammiert werden. Und wenn sich bei Google etwas ändert, das man in der Anwendung nachführen muß (trotz der recht stabilen APIs ist das natürlich schon vorgekommen in den letzten Jahren), muß man bei den beiden Emulationen darauf warten, daß es dort auch entsprechend umgesetzt wird - oder es selbst tun. Es sind ja Open Source Lösungen und man kann die Erweiterungen auch wieder einbringen. Der Zeitliche Versatz bleibt so oder so und es wird immer ein Hinterherprogrammieren bleiben.
Von den beiden Lösungen kommt man mit der "Kleinen" TyphooeAE schneller an ein lauffähiges System aus dem bereitgestellten Code heraus. Hier wird ein Pyhthon-basiertes Build System bereitgestellt und man kann from Scratch bauen.
Die AppScale Lösung ist größer und zielt in jeder Hinsicht auf die größeren Installationen. Zum Ansehen gibt es hier dafür allerdings fertige VMs für Xen und KVM. Dumm nur, daß ich mich gerade wegen der Nutzung unter Windows und Linux für die Oracle Virtual Box entscheiden mußte.

Dienstag, 11. Dezember 2012

On the Fly: URL Formate dynamisch anpassen

Nachdem nun einmal Groovy Codes jederzeit als Klassendefinitionen in das Tangram System eingefügt werden können, bietet es sich an
Mit Groovy kann man in Tangram neben den Ergänzungen der "Bean Schicht" - des Models - durch Shims sehr einfach URL-Formate schreiben und dynamisch jederzeit anpassen.
Dazu wird wieder Code mit MimeType "application/x-grooy" geschrieben. Die Annotation ist diesmal aber "org.tangram.view.link.LinkScheme" oder gleich der Klassenname der zu erstellenden Klasse.
Diese Klasse muß das Interface org.tangram.view.link.LinkScheme implementieren, d.h. Instanzen müssen sowohl URLs interpretieren können als auch aus Objekten URLs generieren können.
Das Tangram-System bietet dabei neben den URLs zur reinen Ansicht der Inhalte sogenannte Action-URLs, mit denen Aktionen - also Controller-Elemente - angesprochen werden sollten. Am Ende so einer Aktion sollte ein Redirekt auf eine reinen Ansichts-URL erfolgen. (Sonst würde bei "Reload" durch den Benutzer die Aktion erneut ausgeführt)

package org.tangram.solution.web.links;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.tangram.Constants;
import org.tangram.content.Content;
import org.tangram.content.BeanFactory;
import org.tangram.controller.DefaultController;
import org.tangram.view.Utils;
import org.tangram.view.TargetDescriptor;
import org.tangram.view.link.Link;
import org.tangram.view.link.LinkScheme;

import org.tangram.solution.web.RootTopic;
import org.tangram.solution.web.Topic;
import org.tangram.solution.web.ImageData;

public class DefaultLinkScheme implements LinkScheme {
 
  private static Log log = LogFactory.getLog(DefaultLinkScheme.class);

  private BeanFactory beanFactory;
 
 
  public void setBeanFactory(BeanFactory factory) {
    beanFactory = factory;
  } // setBeanFactory()
 
 
  public void setDefaultController(DefaultController defaultController) {
    // Automagically set default view
    defaultController.getCustomLinkViews().add(Constants.DEFAULT_VIEW);
  } // setDefaultController ()
 
 
  public Link createLink(HttpServletRequest request, 

                         HttpServletResponse response, Object bean, 
                         String action, String view) {   
      Link result = null;
      if ((action==null)&&(view==null)) {
          if (bean instanceof RootTopic) {
              result = new Link();
              result.setUrl("/");
          } else {
              if ((bean instanceof Topic)||(bean instanceof ImageData)) {
                  String title = "-";
                  String keywords = "-";
                  if (bean instanceof Topic) {
                      try {
                          title = Utils.urlize(((Topic)bean).getTitle());
                          keywords = Utils.urlize(((Topic)bean).getKeywords());
                      } catch (UnsupportedEncodingException uee) {
                          log.error("createLink()", uee);
                      } // try
                  } // if
                  String url = "/"+keywords+"/"+title+

                                    "/"+((Content)bean).getId();
                  result = new Link();
                  result.setUrl(url);
              } // if
          } // if
      } // if
      return result;   
  } // createLink()

 
  private TargetDescriptor rootDescriptor = null;;
 
 
  public TargetDescriptor parseLink(String url, HttpServletResponse response) {
    TargetDescriptor result = null;
    if (url.equals("/")) {
        try {
            if (rootDescriptor==null) {
                List<RootTopic> rs = 

                   beanFactory.listBeans(RootTopic.class, null);
                RootTopic root = null;
                if (rs.size()==1) {
                    root = rs.get(0);
                } else {
                    response.sendError(HttpServletResponse.SC_NOT_FOUND, 

                                "Have "+rs.size()+" RootTopics in data store");
              } // if
                rootDescriptor = new TargetDescriptor(root, null, null);
            } // if
            result = rootDescriptor;
        } catch (Exception e) {
            result = new TargetDescriptor(e, null, null);
        } // try/catch
      } else {
          String id = null;
          Object bean  = null;
          String[] elements = url.split("/");
          for (String element : elements) {
            if (element.indexOf(":") > 0) {
              id = element;
              bean = beanFactory.getBean(element);
            } // if
          } // for
          if (bean != null) {
              result = new TargetDescriptor(bean, null, null);
          } else {
              throw new RuntimeException("no content with id "+id+

                                         " in repository.");
          } // if
      } // if
      return result;
  } // parseLink()
   
} // DefaultLinkScheme

Das Beispiel hier zeigt zum einen das Erzeugen von URLs mit createLink() und das Verarbeiten mit parseLink(). Es ist im Grunde schon gar nicht so simpel sondern erfüllt eine ganze Reihe von Anforderunen:
  • Es sind sprechende URLs umgesetzt, die den Title der Objekte benutzen
  • Die Startseite wird mit / aufgerufen
  • Aktionen laufen auf den DefaultController aus dem System

Samstag, 8. Dezember 2012

Tür zu zum Talentschuppen

Wer gute Ideen in's Web bringen will, muß erst einmal aussortieren, was gute und was schlechte Idee sind. D.h. man muß leider all die schlechten auch in's Web bringen und dann aussortieren, was funktioniert und was nicht.

Alle, die nur wirtschaftlich garantiert erfolgreiches in's Web bringen, leben von denen, die vorher einmal mit Idee gescheitert sind.

Ganz in diesem Sinne hatte Google für die Google App Engine und die Goople Apps kostenlose Angebote, mit denen man schnell, einfach und ohne Risiko Idee in's Web bringen konnte. - Ja, konnte. Denn zeitgleich mit der FTD hat Google heute die kostenlose Version der Google Apps eingestellt - und damit den Weg verbaut, in der gegenwärtigen Fassung Anwendungen aus der Google App Engine mit einer eigenen Domain zu versehen.

Das bedeutet deutlich mehr, als daß man eben keine kostenlosen Mail- und Office-Anwendungen etc. für seine Domains mehr bekommt! Eigene Domains für die Google App Engine sind - und das auch jetzt noch - nur mit den Google Apps zu realisieren.

Auch der in diesem Blog beschriebene Weg, klein anzufangen, und dann im Erfolgsfall einfach wachsen zu können mit der Google Infrastruktur ist damit verbaut. Man kann keine Websites mehr erstellen, die mit - hoffentliche - innovativen, selbst programmierten Ideen - und damit ohne die fertigen Versatzstücke Blog, Sites etc. von Google - kostenlos starten und wenn es viele Teilnehmer gibt, technische wie wirtschaftlich - dann natürlich auch gerne für Google - anfangen zu fliegen.

Jetzt kann jeder für sich durchrechnen, ob die konsteplflichtigen Angebote von Google in diesem Zusammenhang einen Testbalon für die ein oder andere Sache trotzdem sinnvoll erscheinen lassen.

Hier beginnt nun erst einmal die Suche nach den alternativen, die das kostenlose Szenario - und damit auch für Projekte ohne Gewinnabsicht - wieder bedienen.

Bisher war die Rechnung wie folgt:

- Coole Idee mit Google App Engine umsetzen (kostenlos)
- Domain kaufen (Mindestpreis .de Domain 4,65€ pro Jahr)
- Google Apps für die Domain einrichten (kostenlos)

Ich geh dann mal suchen...

Update:
Mini Google-Apps gehen noch, sind aber sehr klein geworden und evtl. zeitlich nur noch begrenzt verfügbar.

Montag, 3. Dezember 2012

Singing the YUI Blues

Im Web gehört es ja zum guten Ton, spätetens seit Google die PageSpeed Tools herausgebracht hat, zusätzlich zur Kompression des HTTP-Inhaltes CSS und JavaScript zu "minifizieren" (ich sage mal lieber minimieren).

Es gehört sogar nicht nur zum guten Ton sondern bringt meßbar bessere Reaktionen in den Browsern und vermindert für Betreiber größerer Websites signifikant die Traffic-Menge. Das alte Argument, daß nachbearbeiten wäre zu CPU-belastend, gilt heute wirklich nicht mehr, wie ich an einer der größten deutschen (europäischen?) Website erfahren und messen konnte. Hier hat man eher Probleme, es nicht zu tun, da irgendwann auch GBit-Bündel von Netzanschlüssen am Kapazitätsende ist - nicht jedoch die CPUs.

Im Tangram sind natürlich alle statischen Dateien im Bereich CSS und JavaScript bereits beim Zusammenbau minifiziert und beim Ausliefern sollte der JSP/Servlet Container komprimieren - oder ein HTTP-Server davor. In der Google App Enginge erledigt das die Infrastruktur von Google auf irgendeiner Ebene für uns ebenfalls automatisch mit. (Und wenn auch die es im großen tun, ist es wohl wirklich kein Lastproblem mehr...) Für Inhalte aus dem Repository ist eine Lösung enthalten, die das CSS oder JavaScript zur Laufzeit minimiert.

Obwohl man das Minimieren dadurch mit dem Komprimieren des gesamten HTTP-Inhaltes verwechseln kann, wird es auch gerne "Kompression" genannt und ist so der Namensgeber des YUICompressors. Dieser ist - s.o. - lange im Tangram integriert, aber nie ohne Probleme, die wir hier endlich einmal nicht elegant aber funktionierend lösen müssen.

Der YUICompressor bietet sehr gute Ergebnisse, ist eigentlich einfach zu benutzen und sehr weit verbreitet. Damit wäre alle in Ordnung und erledigt, da er auch eine einfach zu benutzende Programmierschnittstelle hat, die im Tangram sowohl im Buildsystem als auch in der späteren Ausspielung zur Laufzeit genutzt wird.

Der erste unangenehme Punkt ist, daß aktuelle Versionen des YUICompressors (mindestens für mich) bisher nicht in öffentlichen Maven-Artefakt-Repositories zu finden sind und wir so mit einer nicht ganz neuen Version unterwegs sind.

Der zweite Punkt tut mehr weh: Im JAR-Archiv des YUICompressors befinden sich Klassen, die einige Klasse aus der eingesetzten JavaScript-Bibliothek - einer ebenfalls älteren Fassung von Rhino - überschreiben sollen. Das ist so natürlich eine recht dreckige Lösung, und sie funktioniert auch normalerweise nicht. Das liegt ganz einfach daran, daß im classpath ein JAR mit Namen yui... selten vor einem JAR mit Namen js... zu finden ist. Eine relativ saubere Behandlung der dreckigen Lösung wäre das umschreiben der Archive beim Zusammenbau des Tangram.

Da aber Tangram eher die möglichkeiten bestehender Software nutzen als zu viel Räder neu erfinden soll, Greifen wir lieber in den Werkzeugkasten und sehen, was unser Werkzeugbestand leisten kann.

In diesem Fall ist Gradle - endlich einmal wieder - unser Freund und bietet Möglichkeiten zur Umbenennung beim Zusammenbau. Da alle Zielsysteme den classpath alphabetisch sortiert zusammestellen, reicht es für uns aus, den YUICompressor einfach ein wenig nach vorne zu schieben. Damit sind dieselben Klassennamen immernoch zweimal im classpath vertreten, aber es wird - Erfahrung im Tangram und großen Systemen (s.o.) - nur die funktionierende Version genutzt.

Damit alle Abhängigkeiten im Buildsystem sauber formuliert bleiben, kleben wir unser Pflaster nur in den Anwendungen auf, die mit Tangram gebaut sind - nicht im System selbst.

Im Detail sieht das so aus:

Wir müssen nun den Anwendungen die genaue Version bekannt machen, die bisher nur im System einfach enthalten war:

ext.yui_version="2.4.6"

Das entsprechen JAR lösen wir aus dem automatisch zusammegestellten classpath heraus:

dependencies {
  .
  .
  .
  // To exclude original YUICompressor jar from output war
  providedCompile "com.yahoo.platform.yui:yuicompressor:$yui_version"
  .
  .
  .
}

Jetzt sind wir sowohl den YUICompressor als auch seine Dependencies los und müssen diese nun "von Hand" wieder einfügen. Da tun wir im war.doFirst Zusatz, der in Tangram-Anwendungen "traditionell" schon angepaßt ist:

war.doFirst {
  // Strange way of overwriting things - it must be the first webapp dependency
  if (configurations.webapp.dependencies.size() > 0) {
    String archiveFileName = configurations.webapp.asPath
    int idx = archiveFileName.indexOf(';')
    if (idx >= 0) {
      archiveFileName = archiveFileName.substring(0, idx)
    } // if
    println "unzipping $archiveFileName"
    ant.unzip(src: archiveFileName, dest: "$buildDir/target") 
  } // if
 
  // Missing: YUICompressor - see base system
  // It's not really that important since we don't have any
  // static Stylesheets or JavaScripts are in the examples.
  copy {
    from 'src/main/webapp'
    into "$buildDir/target"
    include '**/**'
  }
 
  // ...and to again include those YUICompressor's dependencies with a different name

  copy {
    from "$buildDir/target"
    into "$buildDir/target"
    include '**/yui*'
    rename 'yui', 'aaa-yui'
  }
  into ('') {
    from "$buildDir/target"
    include 'WEB-INF/lib/aaa-*'
    include 'WEB-INF/lib/js-*'
  }
 
  into ('') {
    from "$buildDir/target"
    exclude 'WEB-INF/lib/**'
  }
}

Hier wird die WAR-Depencency ausgepackt, ggf. statische Ressourcen minifiziert und dann alles außer den Abhängigkeiten sauber verpackt. Und das "alle außer den Abhängigkeiten" müssen wir nun wieder relativieren, da wir ja den YUICompressor aus den Abhängigkeiten entfernt hatte. Also kommt er explizit als "aaa-yuicompressor" wieder rein zusammen mit der Rhino-Bibliothek. Viola - das war's.

Was ist an der Lösung so toll? Nun, in Maven-Szenarien ist mir nichts besseren Eingefallen, als den YUICompressor unter dem albernen Namen in ein lokalen, eigenes Repository einzufügen. Würde ich das hier tun, wäre Tangram nicht mehr mit öffentlichen Maven-Artefakt-Repositories zu bauen. - Nicht gerade ein gute Idee und eigentlich auch keine für nicht öffentliche Projekte.