CacheNamespacePlugin.java
/*
* Copyright 2006-2026 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
*
* https://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.mybatis.generator.plugins;
import static org.mybatis.generator.internal.util.StringUtility.mapStringValueOrElse;
import static org.mybatis.generator.internal.util.StringUtility.mapStringValueOrElseGet;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.kotlin.KotlinFile;
import org.mybatis.generator.api.dom.kotlin.KotlinType;
/**
* This plugin adds a CacheNamespace annotation to generated Java or Kotlin mapper interfaces.
* The plugin accepts the following properties (all are optional):
*
* <ul>
* <li>cache_blocking</li>
* <li>cache_flushInterval</li>
* <li>cache_readWrite</li>
* <li>cache_size</li>
* <li>cache_implementation</li>
* <li>cache_eviction</li>
* <li>cache_skip</li>
* </ul>
*
* <p>All properties (except cache_skip) correspond to properties of the MyBatis CacheNamespace annotation.
* Most properties are passed "as is" to the corresponding properties of the generated
* annotation. The properties "cache_implementation" and "cache_eviction" must be fully qualified class names.
* If specified, the values
* will be added to the import list of the mapper file, and the short names will be used in the generated annotation.
* All properties can be specified at the table level, or on the
* plugin element. The property on the table element will override any
* property on the plugin element.
*
* <p>If the "cache_skip" property is set to "true" - either on the plugin or on a specific table,
* the annotation will not be applied to the generated interface.
*
* @author Jeff Butler
*/
public class CacheNamespacePlugin extends PluginAdapter {
public enum CacheProperty {
BLOCKING("cache_blocking", "blocking", false), //$NON-NLS-1$ //$NON-NLS-2$
FLUSH_INTERVAL("cache_flushInterval", "flushInterval", false), //$NON-NLS-1$ //$NON-NLS-2$
READ_WRITE("cache_readWrite", "readWrite", false), //$NON-NLS-1$ //$NON-NLS-2$
SIZE("cache_size", "size", false), //$NON-NLS-1$ //$NON-NLS-2$
IMPLEMENTATION("cache_implementation", "implementation", true), //$NON-NLS-1$ //$NON-NLS-2$
EVICTION("cache_eviction", "eviction", true); //$NON-NLS-1$ //$NON-NLS-2$
private final String propertyName;
private final String attributeName;
private final boolean isClassName;
CacheProperty(String propertyName, String attributeName, boolean isClassName) {
this.propertyName = propertyName;
this.attributeName = attributeName;
this.isClassName = isClassName;
}
public String getPropertyName() {
return propertyName;
}
public String getAttributeName() {
return attributeName;
}
public boolean isClassName() {
return isClassName;
}
}
@Override
public boolean validate(List<String> arg0) {
return true;
}
@Override
public boolean clientGenerated(Interface interfaze, IntrospectedTable introspectedTable) {
if (!skip(introspectedTable)) {
interfaze.addImportedType(
new FullyQualifiedJavaType("org.apache.ibatis.annotations.CacheNamespace")); //$NON-NLS-1$
Arrays.stream(CacheProperty.values())
.filter(CacheProperty::isClassName)
.map(cp -> getRawPropertyValue(introspectedTable, cp.getPropertyName()))
.filter(Optional::isPresent)
.map(Optional::get)
.map(FullyQualifiedJavaType::new)
.forEach(interfaze::addImportedType);
interfaze.addAnnotation(calculateAnnotation(introspectedTable, ".class")); //$NON-NLS-1$
}
return true;
}
@Override
public boolean mapperGenerated(KotlinFile mapperFile, KotlinType mapper, IntrospectedTable introspectedTable) {
if (!skip(introspectedTable)) {
mapperFile.addImport("org.apache.ibatis.annotations.CacheNamespace"); //$NON-NLS-1$
Arrays.stream(CacheProperty.values())
.filter(CacheProperty::isClassName)
.map(cp -> getRawPropertyValue(introspectedTable, cp.getPropertyName()))
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(mapperFile::addImport);
mapper.addAnnotation(calculateAnnotation(introspectedTable, "::class")); //$NON-NLS-1$
}
return true;
}
private boolean skip(IntrospectedTable introspectedTable) {
return getRawPropertyValue(introspectedTable, "cache_skip") //$NON-NLS-1$
.map("true"::equalsIgnoreCase) //$NON-NLS-1$
.orElse(false);
}
private String calculateAnnotation(IntrospectedTable introspectedTable, String classAccessor) {
String attributes = Arrays.stream(CacheProperty.values())
.map(cp -> calculateAttribute(introspectedTable, cp, classAccessor))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.joining(", ")); //$NON-NLS-1$
return mapStringValueOrElse(attributes,
s -> "@CacheNamespace(" + s + ")", //$NON-NLS-1$ //$NON-NLS-2$
"@CacheNamespace"); //$NON-NLS-1$
}
private Optional<String> calculateAttribute(IntrospectedTable introspectedTable,
CacheProperty cacheProperty,
String classAccessor) {
return getPropertyValueForAttribute(introspectedTable, cacheProperty, classAccessor)
.map(v -> String.format("%s = %s", cacheProperty.getAttributeName(), v)); //$NON-NLS-1$
}
private Optional<String> getPropertyValueForAttribute(IntrospectedTable introspectedTable,
CacheProperty cacheProperty,
String classAccessor) {
Optional<String> value = getRawPropertyValue(introspectedTable, cacheProperty.getPropertyName());
if (cacheProperty.isClassName()) {
value = value.map(FullyQualifiedJavaType::new)
.map(FullyQualifiedJavaType::getShortName)
.map(s -> s + classAccessor);
}
return value;
}
private Optional<String> getRawPropertyValue(IntrospectedTable introspectedTable, String propertyName) {
String value = introspectedTable.getTableConfigurationProperty(propertyName);
if (value == null) {
value = properties.getProperty(propertyName);
}
return mapStringValueOrElseGet(value, Optional::of, Optional::empty);
}
}