package org.ff4j.springjdbc.store;
/*
* #%L ff4j-store-jdbc %% Copyright (C) 2013 Ff4J %% 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. #L%
*/
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.sql.DataSource;
import org.ff4j.core.Feature;
import org.ff4j.core.FeatureStore;
import org.ff4j.exception.FeatureAlreadyExistException;
import org.ff4j.exception.FeatureNotFoundException;
import org.ff4j.exception.GroupNotFoundException;
import org.ff4j.property.Property;
import org.ff4j.springjdbc.store.rowmapper.CustomPropertyRowMapper;
import org.ff4j.springjdbc.store.rowmapper.FeatureRowMapper;
import org.ff4j.springjdbc.store.rowmapper.RoleRowMapper;
import org.ff4j.store.AbstractFeatureStore;
import org.ff4j.store.JdbcQueryBuilder;
import org.ff4j.utils.JdbcUtils;
import org.ff4j.utils.MappingUtil;
import org.ff4j.utils.Util;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
/**
* Implementation of {@link FeatureStore} to work with RDBMS through JDBC.
*
* @author <a href="mailto:cedrick.lunven@gmail.com">Cedrick LUNVEN</a>
*/
@Repository
public class FeatureStoreSpringJdbc extends AbstractFeatureStore {
/** Row Mapper for FlipPoint. */
private static final FeatureRowMapper FMAPPER = new FeatureRowMapper();
/** Mapper for custom properties. */
private static final CustomPropertyRowMapper PMAPPER = new CustomPropertyRowMapper();
/** Error message. */
public static final String FEATURE_IDENTIFIER_CANNOT_BE_NULL_NOR_EMPTY = "Feature identifier cannot be null nor empty";
/** Error message. */
public static final String GROUPNAME_CANNOT_BE_NULL_NOR_EMPTY = "Groupname cannot be null nor empty";
/** SQL DataSource. */
private DataSource dataSource;
/** Access to storage. */
private JdbcTemplate jdbcTemplate;
/** Query builder. */
private JdbcQueryBuilder queryBuilder;
/**
* Default constructor.
*/
public FeatureStoreSpringJdbc() {
}
/**
* Default constructor.
*/
public FeatureStoreSpringJdbc(DataSource ds) {
this.dataSource = ds;
}
/** {@inheritDoc} */
public void enable(String uid) {
Util.assertHasLength(uid);
if (!exist(uid)) {
throw new FeatureNotFoundException(uid);
}
getJdbcTemplate().update(getQueryBuilder().enableFeature(), uid);
}
/** {@inheritDoc} */
public void disable(String uid) {
Util.assertHasLength(uid);
if (!exist(uid)) {
throw new FeatureNotFoundException(uid);
}
getJdbcTemplate().update(getQueryBuilder().disableFeature(), uid);
}
/** {@inheritDoc} */
public boolean exist(String uid) {
Util.assertHasLength(uid);
return 1 == getJdbcTemplate().queryForObject(getQueryBuilder().existFeature(), Integer.class, uid);
}
/** {@inheritDoc} */
public Feature read(String uid) {
Util.assertHasLength(uid);
try {
Feature feature = getJdbcTemplate().queryForObject(
getQueryBuilder().getFeature(), FMAPPER, uid);
readProperties(feature);
readPermissions(feature);
return feature;
} catch(EmptyResultDataAccessException ex) {
throw new FeatureNotFoundException(uid, ex);
}
}
/**
* Query children properties.
*
* @param fp
*/
private void readProperties(Feature fp) {
List<Property<?>> listOfProps = getJdbcTemplate().query(
getQueryBuilder().getFeatureProperties(), PMAPPER, fp.getUid());
for (Property<?> ap : listOfProps) {
fp.getCustomProperties().put(ap.getName(), ap);
}
}
/**
* Query children roles.
*
* @param fp
*/
private void readPermissions(Feature fp) {
fp.getPermissions().addAll(
getJdbcTemplate().query(getQueryBuilder().getRoles(),
new SingleColumnRowMapper<String>(), fp.getUid()));
}
/** {@inheritDoc} */
@Override
@Transactional
public void create(Feature fp) {
Util.assertNotNull(fp);
if (exist(fp.getUid())) {
throw new FeatureAlreadyExistException(fp.getUid());
}
createCoreFeature(fp);
createPermissions(fp);
createProperties(fp);
}
private void createCoreFeature(Feature fp) {
// Transaction wraps the method, could pipe several sql queries
String strategyColumn = null;
String expressionColumn = null;
if (fp.getFlippingStrategy() != null) {
strategyColumn = fp.getFlippingStrategy().getClass().getCanonicalName();
expressionColumn = MappingUtil.fromMap(fp.getFlippingStrategy().getInitParams());
}
getJdbcTemplate().update(getQueryBuilder().createFeature(),
fp.getUid(), fp.isEnable() ? 1 : 0,
fp.getDescription(), strategyColumn,
expressionColumn, fp.getGroup());
}
/**
* Remove all existing permissions and create new.
*
* @param fp
*/
private void createPermissions(Feature fp) {
if (fp.getPermissions() != null) {
getJdbcTemplate().update(getQueryBuilder().deleteRoles(), fp.getUid());
for (String role : fp.getPermissions()) {
getJdbcTemplate().update(getQueryBuilder().addRoleToFeature(), fp.getUid(), role);
}
}
}
/**
* Remove all existing permissions and create new.
*
* @param fp
*/
private void createProperties(Feature fp) {
if (fp.getCustomProperties() != null) {
getJdbcTemplate().update(getQueryBuilder().deleteAllFeatureCustomProperties(), fp.getUid());
for (String propertyName : fp.getCustomProperties().keySet()) {
Property<?> ap = fp.getCustomProperties().get(propertyName);
String fixedValues = null;
if (ap.getFixedValues() != null && ap.getFixedValues().size() > 0) {
fixedValues = ap.getFixedValues().toString();
fixedValues = fixedValues.substring(1, fixedValues.length() - 1);
}
getJdbcTemplate().update(getQueryBuilder().createFeatureProperty(),
ap.getName(), ap.getType(), ap.asString(),
ap.getDescription(), fixedValues, fp.getUid());
}
}
}
/** {@inheritDoc} */
@Override
@Transactional
public void delete(String uid) {
if (!exist(uid)) throw new FeatureNotFoundException(uid);
deletePermissions(uid);
deleteProperties(uid);
deleteCoreFeature(uid);
}
/**
* Delete permissions related to a feature.
*
* @param featureId
* current feature uid
*/
private void deletePermissions(String featureId) {
getJdbcTemplate().update(getQueryBuilder().deleteRoles(), featureId);
}
/**
* Delete properties related to a feature.
*
* @param featureId
* current feature uid
*/
private void deleteProperties(String featureId) {
getJdbcTemplate().update(getQueryBuilder().deleteAllFeatureCustomProperties(), featureId);
}
/**
* Delete core feature.
*
* @param featureId
* current feature uid
*/
private void deleteCoreFeature(String featureId) {
getJdbcTemplate().update(getQueryBuilder().deleteFeature(), featureId);
}
/** {@inheritDoc} */
@Transactional
public void grantRoleOnFeature(String uid, String roleName) {
if (uid == null || uid.isEmpty()) {
throw new IllegalArgumentException(FEATURE_IDENTIFIER_CANNOT_BE_NULL_NOR_EMPTY);
}
if (roleName == null || roleName.isEmpty()) {
throw new IllegalArgumentException("roleName cannot be null nor empty");
}
if (!exist(uid)) {
throw new FeatureNotFoundException(uid);
}
getJdbcTemplate().update(getQueryBuilder().addRoleToFeature(), uid, roleName);
}
/** {@inheritDoc} */
@Transactional
public void removeRoleFromFeature(String uid, String roleName) {
if (uid == null || uid.isEmpty()) {
throw new IllegalArgumentException(FEATURE_IDENTIFIER_CANNOT_BE_NULL_NOR_EMPTY);
}
if (roleName == null || roleName.isEmpty()) {
throw new IllegalArgumentException("roleName cannot be null nor empty");
}
if (!exist(uid)) {
throw new FeatureNotFoundException(uid);
}
getJdbcTemplate().update(getQueryBuilder().deleteFeatureRole(), uid, roleName);
}
/** {@inheritDoc} */
@Transactional
public void enableGroup(String groupName) {
if (groupName == null || groupName.isEmpty()) {
throw new IllegalArgumentException(GROUPNAME_CANNOT_BE_NULL_NOR_EMPTY);
}
if (!existGroup(groupName)) {
throw new GroupNotFoundException(groupName);
}
getJdbcTemplate().update(getQueryBuilder().enableGroup(), groupName);
}
/** {@inheritDoc} */
@Transactional
public void disableGroup(String groupName) {
if (groupName == null || groupName.isEmpty()) {
throw new IllegalArgumentException(GROUPNAME_CANNOT_BE_NULL_NOR_EMPTY);
}
if (!existGroup(groupName)) {
throw new GroupNotFoundException(groupName);
}
getJdbcTemplate().update(getQueryBuilder().disableGroup(), groupName);
}
/** {@inheritDoc} */
public boolean existGroup(String groupName) {
if (groupName == null || groupName.isEmpty()) {
throw new IllegalArgumentException(GROUPNAME_CANNOT_BE_NULL_NOR_EMPTY);
}
int count = getJdbcTemplate().queryForObject(
getQueryBuilder().existGroup(), Integer.class, groupName);
return count > 0;
}
/** {@inheritDoc} */
public Map<String, Feature> readGroup(String groupName) {
if (groupName == null || groupName.isEmpty()) {
throw new IllegalArgumentException(GROUPNAME_CANNOT_BE_NULL_NOR_EMPTY);
}
if (!existGroup(groupName)) {
throw new GroupNotFoundException(groupName);
}
LinkedHashMap<String, Feature> mapFP = new LinkedHashMap<String, Feature>();
List<Feature> lFp = getJdbcTemplate().query(getQueryBuilder().getFeatureOfGroup(), FMAPPER, groupName);
for (Feature flipPoint : lFp) {
mapFP.put(flipPoint.getUid(), flipPoint);
}
return mapFP;
}
/** {@inheritDoc} */
@Transactional
public void addToGroup(String uid, String groupName) {
if (uid == null || uid.isEmpty()) {
throw new IllegalArgumentException(FEATURE_IDENTIFIER_CANNOT_BE_NULL_NOR_EMPTY);
}
if (groupName == null || groupName.isEmpty()) {
throw new IllegalArgumentException(GROUPNAME_CANNOT_BE_NULL_NOR_EMPTY);
}
if (!exist(uid)) {
throw new FeatureNotFoundException(uid);
}
getJdbcTemplate().update(getQueryBuilder().addFeatureToGroup(), groupName, uid);
}
/** {@inheritDoc} */
@Transactional
public void removeFromGroup(String uid, String groupName) {
if (uid == null || uid.isEmpty()) {
throw new IllegalArgumentException(FEATURE_IDENTIFIER_CANNOT_BE_NULL_NOR_EMPTY);
}
if (groupName == null || groupName.isEmpty()) {
throw new IllegalArgumentException(GROUPNAME_CANNOT_BE_NULL_NOR_EMPTY);
}
if (!exist(uid)) {
throw new FeatureNotFoundException(uid);
}
if (!existGroup(groupName)) {
throw new GroupNotFoundException(groupName);
}
getJdbcTemplate().update(getQueryBuilder().addFeatureToGroup(), "", uid);
}
/** {@inheritDoc} */
public Map<String, Feature> readAll() {
LinkedHashMap<String, Feature> mapFP = new LinkedHashMap<String, Feature>();
List<Feature> lFp = getJdbcTemplate().query(getQueryBuilder().getAllFeatures(), FMAPPER);
for (Feature flipPoint : lFp) {
mapFP.put(flipPoint.getUid(), flipPoint);
}
// Populating Roles
RoleRowMapper rrm = new RoleRowMapper();
getJdbcTemplate().query(getQueryBuilder().getAllRoles(), rrm);
Map<String, Set<String>> roles = rrm.getRoles();
for (Map.Entry<String,Set<String>> featId : roles.entrySet()) {
if (mapFP.containsKey(featId.getKey())) {
mapFP.get(featId.getKey()).getPermissions().addAll(featId.getValue());
}
}
return mapFP;
}
/** {@inheritDoc} */
public Set<String> readAllGroups() {
Set<String> setOfGroup = new HashSet<String>();
setOfGroup.addAll(getJdbcTemplate().query(
getQueryBuilder().getAllGroups(), new SingleColumnRowMapper<String>()));
setOfGroup.remove(null);
setOfGroup.remove("");
return setOfGroup;
}
/** {@inheritDoc} */
@Transactional
public void update(Feature newFeature) {
Util.assertNotNull(newFeature);
delete(newFeature.getUid());
create(newFeature);
}
/** {@inheritDoc} */
@Override
@Transactional
public void createSchema() {
JdbcQueryBuilder qb = getQueryBuilder();
if (!JdbcUtils.isTableExist(dataSource, qb.getTableNameFeatures())) {
getJdbcTemplate().update(qb.sqlCreateTableFeatures());
}
if (!JdbcUtils.isTableExist(dataSource, qb.getTableNameCustomProperties())) {
getJdbcTemplate().update(qb.sqlCreateTableCustomProperties());
}
if (!JdbcUtils.isTableExist(dataSource, qb.getTableNameRoles())) {
getJdbcTemplate().update(qb.sqlCreateTableRoles());
}
}
/** {@inheritDoc} */
@Transactional
public void clear() {
getJdbcTemplate().update(getQueryBuilder().deleteAllRoles());
getJdbcTemplate().update(getQueryBuilder().deleteAllCustomProperties());
getJdbcTemplate().update(getQueryBuilder().deleteAllFeatures());
}
/**
* @param dataSource
* the dataSource to set
*/
@Required
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* Getter accessor for attribute 'jdbcTemplate'.
*
* @return current value of 'jdbcTemplate'
*/
public JdbcTemplate getJdbcTemplate() {
if (jdbcTemplate == null) {
if (dataSource == null) {
throw new IllegalStateException("ff4j-jdbc: DatabaseStore has not been properly initialized, datasource is null");
}
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
return jdbcTemplate;
}
/**
* @return the queryBuilder
*/
public JdbcQueryBuilder getQueryBuilder() {
if (queryBuilder == null) {
queryBuilder = new JdbcQueryBuilder();
}
return queryBuilder;
}
/**
* @param queryBuilder the queryBuilder to set
*/
public void setQueryBuilder(JdbcQueryBuilder queryBuilder) {
this.queryBuilder = queryBuilder;
}
}