blob: 6ce50d4ab616317ab2ef3c0617adaf08a28dbc02 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.
*/
import _ from 'lodash';
import {Bean} from './Beans';
import AbstractTransformer from './AbstractTransformer';
import StringBuilder from './StringBuilder';
import VersionService from 'app/services/Version.service';
const versionService = new VersionService();
export default class IgniteSpringTransformer extends AbstractTransformer {
static escapeXml(str = '') {
return str.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/>/g, '>')
.replace(/</g, '&lt;');
}
static commentBlock(sb, ...lines) {
if (lines.length > 1) {
sb.append('<!--');
_.forEach(lines, (line) => sb.append(` ${line}`));
sb.append('-->');
}
else
sb.append(`<!-- ${_.head(lines)} -->`);
}
static appendBean(sb, bean, appendId) {
const beanTags = [];
if (appendId)
beanTags.push(`id="${bean.id}"`);
beanTags.push(`class="${bean.clsName}"`);
if (bean.factoryMtd)
beanTags.push(`factory-method="${bean.factoryMtd}"`);
sb.startBlock(`<bean ${beanTags.join(' ')}>`);
_.forEach(bean.arguments, (arg) => {
if (arg.clsName === 'MAP') {
sb.startBlock('<constructor-arg>');
this._constructMap(sb, arg);
sb.endBlock('</constructor-arg>');
}
else if (_.isNil(arg.value)) {
sb.startBlock('<constructor-arg>');
sb.append('<null/>');
sb.endBlock('</constructor-arg>');
}
else if (arg.constant) {
sb.startBlock('<constructor-arg>');
sb.append(`<util:constant static-field="${arg.clsName}.${arg.value}"/>`);
sb.endBlock('</constructor-arg>');
}
else if (arg.clsName === 'BEAN') {
sb.startBlock('<constructor-arg>');
this.appendBean(sb, arg.value);
sb.endBlock('</constructor-arg>');
}
else
sb.append(`<constructor-arg value="${this._toObject(arg.clsName, arg.value)}"/>`);
});
this._setProperties(sb, bean);
sb.endBlock('</bean>');
}
static _toObject(clsName, val) {
const items = _.isArray(val) ? val : [val];
if (clsName === 'EVENTS')
return ['<list>', ..._.map(items, (item) => ` <util:constant static-field="${item.class}.${item.label}"/>`), '</list>'];
return _.map(items, (item) => {
switch (clsName) {
case 'PROPERTY':
case 'PROPERTY_CHAR':
case 'PROPERTY_INT':
return `\${${item}}`;
case 'java.lang.Class':
return this.javaTypes.fullClassName(item);
case 'long':
return `${item}`;
case 'java.lang.String':
case 'PATH':
case 'PATH_ARRAY':
return this.escapeXml(item);
default:
return item;
}
});
}
static _isBean(clsName) {
return this.javaTypes.nonBuiltInClass(clsName) && this.javaTypesNonEnum.nonEnum(clsName) && _.includes(clsName, '.');
}
static _setCollection(sb, prop) {
sb.startBlock(`<property name="${prop.name}">`);
sb.startBlock('<list>');
_.forEach(prop.items, (item, idx) => {
if (this._isBean(prop.typeClsName)) {
if (idx !== 0)
sb.emptyLine();
this.appendBean(sb, item);
}
else
sb.append(`<value>${item}</value>`);
});
sb.endBlock('</list>');
sb.endBlock('</property>');
}
static _constructMap(sb, map) {
sb.startBlock('<map>');
_.forEach(map.entries, (entry) => {
const key = entry[map.keyField];
const val = entry[map.valField];
const isKeyBean = key instanceof Bean || this._isBean(map.keyClsName);
const isValBean = val instanceof Bean || this._isBean(map.valClsName);
if (isKeyBean || isValBean) {
sb.startBlock('<entry>');
sb.startBlock('<key>');
if (isKeyBean)
this.appendBean(sb, key);
else
sb.append(this._toObject(map.keyClsName, key));
sb.endBlock('</key>');
if (!_.isArray(val))
sb.startBlock('<value>');
if (isValBean)
this.appendBean(sb, val);
else
sb.append(this._toObject(map.valClsNameShow || map.valClsName, val));
if (!_.isArray(val))
sb.endBlock('</value>');
sb.endBlock('</entry>');
}
else
sb.append(`<entry key="${this._toObject(map.keyClsName, key)}" value="${this._toObject(map.valClsName, val)}"/>`);
});
sb.endBlock('</map>');
}
/**
*
* @param {StringBuilder} sb
* @param {Bean} bean
* @returns {StringBuilder}
*/
static _setProperties(sb, bean) {
_.forEach(bean.properties, (prop, idx) => {
switch (prop.clsName) {
case 'DATA_SOURCE':
const valAttr = prop.name === 'dataSource' ? 'ref' : 'value';
sb.append(`<property name="${prop.name}" ${valAttr}="${prop.id}"/>`);
break;
case 'EVENT_TYPES':
sb.startBlock(`<property name="${prop.name}">`);
if (prop.eventTypes.length === 1) {
const evtGrp = _.head(prop.eventTypes);
sb.append(`<util:constant static-field="${evtGrp.class}.${evtGrp.label}"/>`);
}
else {
sb.startBlock('<list>');
_.forEach(prop.eventTypes, (evtGrp, ix) => {
ix > 0 && sb.emptyLine();
sb.append(`<!-- EventType.${evtGrp.label} -->`);
_.forEach(evtGrp.events, (event) =>
sb.append(`<util:constant static-field="${evtGrp.class}.${event}"/>`));
});
sb.endBlock('</list>');
}
sb.endBlock('</property>');
break;
case 'ARRAY':
case 'PATH_ARRAY':
case 'COLLECTION':
this._setCollection(sb, prop);
break;
case 'MAP':
sb.startBlock(`<property name="${prop.name}">`);
this._constructMap(sb, prop);
sb.endBlock('</property>');
break;
case 'java.util.Properties':
sb.startBlock(`<property name="${prop.name}">`);
sb.startBlock('<props>');
_.forEach(prop.entries, (entry) => {
sb.append(`<prop key="${entry.name}">${entry.value}</prop>`);
});
sb.endBlock('</props>');
sb.endBlock('</property>');
break;
case 'BEAN':
sb.startBlock(`<property name="${prop.name}">`);
this.appendBean(sb, prop.value);
sb.endBlock('</property>');
break;
default:
sb.append(`<property name="${prop.name}" value="${this._toObject(prop.clsName, prop.value)}"/>`);
}
this._emptyLineIfNeeded(sb, bean.properties, idx);
});
return sb;
}
/**
* Build final XML.
*
* @param {Bean} cfg Ignite configuration.
* @param {Object} targetVer Version of Ignite for generated project.
* @param {Boolean} clientNearCaches
* @returns {StringBuilder}
*/
static igniteConfiguration(cfg, targetVer, clientNearCaches) {
const available = versionService.since.bind(versionService, targetVer.ignite);
const sb = new StringBuilder();
// 0. Add header.
sb.append('<?xml version="1.0" encoding="UTF-8"?>');
sb.emptyLine();
this.mainComment(sb);
sb.emptyLine();
// 1. Start beans section.
sb.startBlock([
'<beans xmlns="http://www.springframework.org/schema/beans"',
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',
' xmlns:util="http://www.springframework.org/schema/util"',
' xsi:schemaLocation="http://www.springframework.org/schema/beans',
' http://www.springframework.org/schema/beans/spring-beans.xsd',
' http://www.springframework.org/schema/util',
' http://www.springframework.org/schema/util/spring-util.xsd">']);
// 2. Add external property file
if (this.hasProperties(cfg)) {
this.commentBlock(sb, 'Load external properties file.');
sb.startBlock('<bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">');
sb.append('<property name="location" value="classpath:secret.properties"/>');
sb.endBlock('</bean>');
sb.emptyLine();
}
// 3. Add data sources.
const dataSources = this.collectDataSources(cfg);
if (dataSources.length) {
this.commentBlock(sb, 'Data source beans will be initialized from external properties file.');
_.forEach(dataSources, (ds) => {
this.appendBean(sb, ds, true);
sb.emptyLine();
});
}
_.forEach(clientNearCaches, (cache) => {
this.commentBlock(sb, `Configuration of near cache for cache "${cache.name}"`);
this.appendBean(sb, this.generator.cacheNearClient(cache, available), true);
sb.emptyLine();
});
// 3. Add main content.
this.appendBean(sb, cfg);
// 4. Close beans section.
sb.endBlock('</beans>');
return sb;
}
static cluster(cluster, targetVer, client) {
const cfg = this.generator.igniteConfiguration(cluster, targetVer, client);
const clientNearCaches = client ? _.filter(cluster.caches, (cache) =>
cache.cacheMode === 'PARTITIONED' && _.get(cache, 'clientNearConfiguration.enabled')) : [];
return this.igniteConfiguration(cfg, targetVer, clientNearCaches);
}
}