/* * Copyright 2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.springsource.greenhouse.develop; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import javax.inject.Inject; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.security.crypto.encrypt.TextEncryptor; import org.springframework.security.crypto.keygen.KeyGenerators; import org.springframework.security.crypto.keygen.StringKeyGenerator; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import com.springsource.greenhouse.utils.SlugUtils; /** * AppRepository implementation that stores App data in a relational database using the JDBC API. * ApiKeys and secrets, as well as accessTokens and secrets, are encrypted for storage using a {@link StringEncryptor}. * @author Keith Donald */ @Repository public class JdbcAppRepository implements AppRepository { private JdbcTemplate jdbcTemplate; private TextEncryptor encryptor; private StringKeyGenerator keyGenerator = KeyGenerators.string(); @Inject public JdbcAppRepository(JdbcTemplate jdbcTemplate, TextEncryptor encryptor) { this.jdbcTemplate = jdbcTemplate; this.encryptor = encryptor; } public List<AppSummary> findAppSummaries(Long accountId) { return jdbcTemplate.query(SELECT_APPS, appSummaryMapper, accountId); } public App findAppBySlug(Long accountId, String slug) { return jdbcTemplate.queryForObject(SELECT_APP_BY_SLUG, appMapper, accountId, slug); } public App findAppByApiKey(String apiKey) throws InvalidApiKeyException { try { return jdbcTemplate.queryForObject(SELECT_APP_BY_API_KEY, appMapper, encryptor.encrypt(apiKey)); } catch (EmptyResultDataAccessException e) { throw new InvalidApiKeyException(apiKey); } } public String updateApp(Long accountId, String slug, AppForm form) { String newSlug = createSlug(form.getName()); jdbcTemplate.update(UPDATE_APP_FORM, form.getName(), newSlug, form.getDescription(), form.getOrganization(), form.getWebsite(), form.getCallbackUrl(), accountId, slug); return newSlug; } public void deleteApp(Long accountId, String slug) { jdbcTemplate.update(DELETE_APP, accountId, slug); } public AppForm getNewAppForm() { return new AppForm(); } public AppForm getAppForm(Long accountId, String slug) { return jdbcTemplate.queryForObject(SELECT_APP_FORM, appFormMapper, accountId, slug); } @Transactional public String createApp(Long accountId, AppForm form) { String slug = createSlug(form.getName()); String encryptedApiKey = encryptor.encrypt(keyGenerator.generateKey()); String encryptedSecret = encryptor.encrypt(keyGenerator.generateKey()); jdbcTemplate.update(INSERT_APP, form.getName(), slug, form.getDescription(), form.getOrganization(), form.getWebsite(), encryptedApiKey, encryptedSecret, form.getCallbackUrl()); Long appId = jdbcTemplate.queryForLong("call identity()"); jdbcTemplate.update(INSERT_APP_DEVELOPER, appId, accountId); return slug; } // internal helpers private String createSlug(String appName) { return SlugUtils.toSlug(appName); } private static final String SELECT_APPS = "select a.name, a.slug, a.description from App a inner join AppDeveloper d on a.id = d.app where d.member = ?"; private static final String SELECT_APP_BY_SLUG = "select a.name, a.slug, a.description, a.apiKey, a.secret, a.callbackUrl from App a inner join AppDeveloper d on a.id = d.app where d.member = ? and a.slug = ?"; private static final String SELECT_APP_BY_API_KEY = "select a.name, a.slug, a.description, a.apiKey, a.secret, a.callbackUrl from App a where a.apiKey = ?"; private static final String SELECT_APP_FORM = "select a.name, a.description, a.organization, a.website, a.callbackUrl from App a inner join AppDeveloper d on a.id = d.app where d.member = ? and a.slug = ?"; private static final String UPDATE_APP_FORM = "update App set name = ?, slug = ?, description = ?, organization = ?, website = ?, callbackUrl = ? where exists(select 1 from AppDeveloper where member = ?) and slug = ?"; private static final String DELETE_APP = "delete from App where exists(select 1 from AppDeveloper where member = ?) and slug = ?"; private static final String INSERT_APP = "insert into App (name, slug, description, organization, website, apiKey, secret, callbackUrl) values (?, ?, ?, ?, ?, ?, ?, ?)"; private static final String INSERT_APP_DEVELOPER = "insert into AppDeveloper (app, member) values (?, ?)"; private RowMapper<AppSummary> appSummaryMapper = new RowMapper<AppSummary>() { public AppSummary mapRow(ResultSet rs, int rowNum) throws SQLException { // TODO this is currently hardcoded String iconUrl = "http://images.greenhouse.springsource.org/apps/icon-default-app.png"; return new AppSummary(rs.getString("name"), iconUrl, rs.getString("description"), rs.getString("slug")); } }; private RowMapper<App> appMapper = new RowMapper<App>() { public App mapRow(ResultSet rs, int rowNum) throws SQLException { return new App(appSummaryMapper.mapRow(rs, rowNum), encryptor.decrypt(rs.getString("apiKey")), encryptor.decrypt(rs.getString("secret")), rs.getString("callbackUrl")); } }; private RowMapper<AppForm> appFormMapper = new RowMapper<AppForm>() { public AppForm mapRow(ResultSet rs, int rowNum) throws SQLException { AppForm form = new AppForm(); form.setName(rs.getString("name")); form.setDescription(rs.getString("description")); form.setOrganization(rs.getString("organization")); form.setWebsite(rs.getString("website")); form.setCallbackUrl(rs.getString("callbackUrl")); return form; } }; }