/* * Copyright 2010-2013 Ning, Inc. * Copyright 2014-2017 Groupon, Inc * Copyright 2014-2017 The Billing Project, LLC * * The Billing Project licenses this file to you 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 org.killbill.billing.util.entity.dao; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.regex.Matcher; import org.killbill.billing.util.dao.EntityHistoryModelDaoMapperFactory; import org.killbill.billing.util.entity.Entity; import org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapperFactory; import org.skife.jdbi.v2.Query; import org.skife.jdbi.v2.SQLStatement; import org.skife.jdbi.v2.sqlobject.SqlStatementCustomizer; import org.skife.jdbi.v2.sqlobject.SqlStatementCustomizingAnnotation; import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper; import org.skife.jdbi.v2.sqlobject.stringtemplate.StringTemplate3StatementLocator; import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator; import org.skife.jdbi.v2.tweak.StatementLocator; @SqlStatementCustomizingAnnotation(EntitySqlDaoStringTemplate.EntitySqlDaoLocatorFactory.class) @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface EntitySqlDaoStringTemplate { static final String DEFAULT_VALUE = " ~ "; String value() default DEFAULT_VALUE; public static class EntitySqlDaoLocatorFactory extends UseStringTemplate3StatementLocator.LocatorFactory { final static boolean enableGroupTemplateCaching = Boolean.parseBoolean(System.getProperty("killbill.jdbi.allow.stringTemplateGroupCaching", "true")); static ConcurrentMap<String, StatementLocator> locatorCache = new ConcurrentHashMap<String, StatementLocator>(); // // This is only needed to compute the key for the cache -- whether we get a class or a pathname (string) // // (Similar to what jdbi is doing (StringTemplate3StatementLocator)) // private final static String sep = "/"; // *Not* System.getProperty("file.separator"), which breaks in jars public static String mungify(final Class claz) { final String path = "/" + claz.getName(); return path.replaceAll("\\.", Matcher.quoteReplacement(sep)) + ".sql.stg"; } private static StatementLocator getLocator(final String locatorPath) { if (enableGroupTemplateCaching && locatorCache.containsKey(locatorPath)) { return locatorCache.get(locatorPath); } final StringTemplate3StatementLocator.Builder builder = StringTemplate3StatementLocator.builder(locatorPath) .shouldCache() .withSuperGroup(EntitySqlDao.class) .allowImplicitTemplateGroup() .treatLiteralsAsTemplates(); final StatementLocator locator = builder.build(); if (enableGroupTemplateCaching) { locatorCache.put(locatorPath, locator); } return locator; } public SqlStatementCustomizer createForType(final Annotation annotation, final Class sqlObjectType) { final EntitySqlDaoStringTemplate a = (EntitySqlDaoStringTemplate) annotation; final String locatorPath = DEFAULT_VALUE.equals(a.value()) ? mungify(sqlObjectType) : a.value(); final StatementLocator l = getLocator(locatorPath); return new SqlStatementCustomizer() { public void apply(final SQLStatement statement) { statement.setStatementLocator(l); if (statement instanceof Query) { final Query query = (Query) statement; // Find the model class associated with this sqlObjectType (which is a SqlDao class) to register its mapper // If a custom mapper is defined via @RegisterMapper, don't register our generic one if (sqlObjectType.getGenericInterfaces() != null && sqlObjectType.getAnnotation(RegisterMapper.class) == null) { for (int i = 0; i < sqlObjectType.getGenericInterfaces().length; i++) { if (sqlObjectType.getGenericInterfaces()[i] instanceof ParameterizedType) { final ParameterizedType type = (ParameterizedType) sqlObjectType.getGenericInterfaces()[i]; for (int j = 0; j < type.getActualTypeArguments().length; j++) { final Type modelType = type.getActualTypeArguments()[j]; if (modelType instanceof Class) { final Class modelClazz = (Class) modelType; if (Entity.class.isAssignableFrom(modelClazz)) { query.registerMapper(new LowerToCamelBeanMapperFactory(modelClazz)); query.registerMapper(new EntityHistoryModelDaoMapperFactory(modelClazz, sqlObjectType)); } } } } } } } } }; } public SqlStatementCustomizer createForMethod(final Annotation annotation, final Class sqlObjectType, final Method method) { throw new UnsupportedOperationException("Not Defined on Method"); } public SqlStatementCustomizer createForParameter(final Annotation annotation, final Class sqlObjectType, final Method method, final Object arg) { throw new UnsupportedOperationException("Not defined on parameter"); } } }