Tuesday, September 6, 2016

Dynamic baseref with angular 2

Angular 2 determines how to load 'relative' links using this tag in the main index:

<base href="/">&lt
This isn't a great thing if you want to deploy to other than the root directory of a server. You can find many examples of this causing grief for developers.

Short of manually changing base href before you deploy, the most common solution seems to be to have a little bit of javascript emit the base href into the document before angular loads. Yuck. I managed to work up a more D.I. way to do this that I'll share. No guarantees that this is going to keep working in subsequent RCs (I'm still on 2.0.0-rc.5):

import {RouterModule} from '@angular/router';
import {APP_BASE_HREF} from '@angular/common';
import {ModuleWithProviders} from '@angular/core';
/* all your other imports */


const appRoutingProviders: any[] = [
  {
    provide: APP_BASE_HREF,
    useFactory: () => {
      return window.location.pathname;
    }, deps: [  ]
  }
];
const routingModule: ModuleWithProviders = RouterModule.forRoot(routingTable, {enableTracing: false, useHash: true});

@NgModule({
  declarations: [
    AppComponent, /* list all your other components here */
  ],
  imports: [
    BrowserModule,
    CommonModule,
    FormsModule,
    HttpModule,
    routingModule
  ],
  providers: [appRoutingProviders],
  bootstrap: [AppComponent],
  entryComponents: [AppComponent]
})
export class AppModule {
}


The basic idea is to build a bridge into the initial path then use that as the base. I've been looking for a less error-prone mechanism and haven't found it yet - certainly this could be a little more robust in the form of error checking. If you're like me and want to deploy by scp then it's better than the alternative, having to edit index.html based on where you deploy.

Sunday, May 8, 2016

Changing the catalog on a JdbcTemplate (with Spring)

I had a requirement to change the catalog (i.e. database) with JdbcTemplate. I searched around for a while for an answer and, as is often the case, found a solution scattered across several Stack Overflow and other posts. My configuration is:
  • Spring Framework 4.2.5.RELEASE
  • spring-jdbc (for JdbcTemplate)
  • tomcat-jdbc 8.0.18 (for org.apache.tomcat.jdbc.pool.DataSource)

The key to making this all work is to to extend Spring's DelegatingDataSource and override getConnection() to set the catalog you want:

public class CatalogSpecificDataSource extends DelegatingDataSource {

    private final String catalog;

    public CatalogSpecificDataSource(DataSource orig, String catalog) {
        super(orig);
        this.catalog = catalog;
    }

    @Override
    public Connection getConnection() throws SQLException {
        Connection conn = super.getConnection();
        conn.setCatalog(catalog);
        return conn;
    }
}

The next problem is cleanup: how do you ensure that the connection is reset to the default catalog once it's placed into the pool? The answer is in the tomcat connection pool configuration:

    PoolProperties p = new PoolProperties();
    p.setDefaultCatalog("customers");
    /*
     * set your connection properties here - driver, connection string, etc.
     * then, when you're done with that, add the interceptors
     */
    p.setJdbcInterceptors(
        org.apache.tomcat.jdbc.pool.interceptor.ConnectionState.class.getName() + ";" +
        org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer.class.getName());
    DataSource datasource = new DataSource();
    datasource.setPoolProperties(p);

The ConnectionState interceptor remembers if you changed the catalog and will set it back. When you create your JdbcTemplate you can now:

    JdbcTemplate t = new CatalogDataSource(realDataSource, "someOtherCatalog");

or use Spring's idea of assisted injection. To do that:

Step 1. Create a factory interface

public interface CatalogSpecificDataSourceFactory {
    javax.sql.DataSource getDatasource(String catalog);
}

Step 2. Create a spring bean configuration:

    @Bean
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public CatalogSpecificDataSourceFactory getCatalogSpecificDatasourceFactory() {
        return catalog -> new CatalogSpecificDataSource(getCatalogSpecificDatasource(), catalog);
    }

Step 3. When you want to use it, inject a CatalogSpecificDataSourceFactory then use it to create your Datasource:

    DataSource ds = catalogSpecificDataSoruceFactory.getCatalogSpecificDatasource("booger");
    JdbcTemplate t = new JdbcTemplate(ds);

Or, if you want to get even fancier, set up another assisted factory. In my case, following the assisted injection pattern once again:

    public interface BatchMissionWriterFactory {
        BatchMissionWriter create(String databaseName);
    }

    /* then, in the config: */
    @Bean
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public BatchWidgetWriterFactory getBatchMissionWriterFactory()
    {
        return catalog -> new BatchWidgetWriterFactory (getCatalogDataSourceFactory().getCatalogSpecificDatasource(catalog));
    }

As usual, hope this is helpful to someone - feedback welcome.

Saturday, January 9, 2016

Reverse engineering a hot tub

I changed the water in my hot tub today and so I had a lot of time on my hands, so I took the controller cover off and tried to figure out how it works. My main purpose was to try to figure out how the control panel interfaces, mainly so that I can contemplate some kind of remote monitoring interface. I know that some models of Balboa controllers have WiFi options, so I figured there must be a communications interface somewhere. There definitely is. The existing (very simple) control panel is connected via an 8-pin RJ-45, and there's a spare one right next to it. There is one removable chip on the board labeled with something looking like a version number. I peeled the sticker off and, surprise it's a PIC18F2420 microcontroller. That's great - I already have programming tools and a C compiler for that. My next step is to try to find a schematic for this board. If I can do that then the feasibility of entirely replacing the control software is high. The hardest part probably would be figuring out how to talk to their existing control panel.