/* Copyright (C) 2019 AGNITAS AG (https://www.agnitas.org) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ package com.agnitas.emm.core.target.web.listener; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.sql.Blob; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.sql.DataSource; import org.agnitas.dao.impl.mapper.IntegerRowMapper; import org.agnitas.emm.core.commons.util.ConfigValue; import org.agnitas.target.TargetRepresentation; import org.agnitas.target.impl.TargetRepresentationImpl; import org.apache.log4j.Logger; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import com.agnitas.emm.core.target.eql.EqlFacade; import com.agnitas.emm.core.target.eql.emm.legacy.TargetRepresentationToEqlConversionException; public class TargetGroupMigrationListener implements ServletContextListener { /** The logger. */ private static final transient Logger LOGGER = Logger.getLogger(TargetGroupMigrationListener.class); private static final class TargetGroupData { public final int targetID; public final TargetRepresentation targetRepresentation; public TargetGroupData(final int targetID, final TargetRepresentation targetRepresentation) { this.targetID = targetID; this.targetRepresentation = targetRepresentation; } } private static final class TargetGroupDataRowMapper implements RowMapper<TargetGroupData> { @Override public final TargetGroupData mapRow(final ResultSet resultSet, final int row) throws SQLException { final int targetId = resultSet.getInt("target_id"); final TargetRepresentation targetRepresentation = makeTargetRepresentationFromSerializedData(resultSet); return new TargetGroupData(targetId, targetRepresentation); } private TargetRepresentation makeTargetRepresentationFromSerializedData(final ResultSet resultSet) throws SQLException { Blob targetRepresentationBlob = resultSet.getBlob("target_representation"); if (resultSet.wasNull() || targetRepresentationBlob.length() == 0) { return new TargetRepresentationImpl(); } else { try { try(final InputStream lobStream = targetRepresentationBlob.getBinaryStream()) { try(final ObjectInputStream stream = new ObjectInputStream(lobStream)) { return (TargetRepresentation) stream.readObject(); } } } catch(final IOException e) { throw new SQLException("Error reading serialized form", e); } catch (ClassNotFoundException e) { throw new SQLException("Error de-serialized target group", e); } finally { targetRepresentationBlob.free(); } } } } @Override public final void contextDestroyed(final ServletContextEvent servletContextEvent) { // Nothing to do here } @Override public final void contextInitialized(final ServletContextEvent servletContextEvent) { final ServletContext servletContext = servletContextEvent.getServletContext(); final WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext); final DataSource dataSource = webApplicationContext.getBean("dataSource", DataSource.class); final EqlFacade eqlFacade = webApplicationContext.getBean("EqlFacade", EqlFacade.class); migrateTargetGroupsOfMarkedCompanies(dataSource, eqlFacade); } private static final void migrateTargetGroupsOfMarkedCompanies(final DataSource dataSource, final EqlFacade eqlFacade) { final List<Integer> companyIds = listMarkedCompanies(dataSource); for (final int companyId : companyIds) { try { migrateTargetGroupsOfCompany(companyId, dataSource, eqlFacade); removeCompanyMarker(companyId, dataSource); } catch(final Exception e) { LOGGER.error(String.format("Error migrating target groups of company %d", companyId), e); } } } private static final void migrateTargetGroupsOfCompany(final int companyId, final DataSource dataSource, final EqlFacade eqlFacade) { final List<TargetGroupData> targetGroups = listTargetGroups(companyId, dataSource); for (final TargetGroupData targetGroup : targetGroups) { try { migrateTargetGroup(companyId, targetGroup, dataSource, eqlFacade); } catch(final Exception e) { LOGGER.error(String.format("Error migrating target group %d of company %d", targetGroup.targetID, companyId), e); } } } private static final void migrateTargetGroup(int companyId, final TargetGroupData targetGroup, final DataSource dataSource, final EqlFacade eqlFacade) throws TargetRepresentationToEqlConversionException { if (companyId == 0) { // Migrate listsplit targetgroups available for all clients companyId = 1; } final String eql = eqlFacade.convertTargetRepresentationToEql(targetGroup.targetRepresentation, companyId); final String sql = "UPDATE dyn_target_tbl SET eql = ?, target_representation = NULL WHERE target_id = ?"; final JdbcTemplate template = new JdbcTemplate(dataSource); template.update(sql,eql, targetGroup.targetID); if(LOGGER.isInfoEnabled()) { LOGGER.info(String.format("Migrated target group %d", targetGroup.targetID)); } } private static final List<Integer> listMarkedCompanies(final DataSource dataSource) { final JdbcTemplate template = new JdbcTemplate(dataSource); return template.query("SELECT company_id FROM company_info_tbl WHERE cname = ? and cvalue = 'true'", new IntegerRowMapper(), ConfigValue.MigrateTargetGroupsOnStartup.toString()); } private static final List<TargetGroupData> listTargetGroups(final int companyId, final DataSource dataSource) { final String sql = "SELECT target_id, target_representation FROM dyn_target_tbl WHERE company_id = ? AND (locked IS NULL OR locked = 0) AND (eql IS NULL OR eql = '') AND target_representation IS NOT NULL"; final JdbcTemplate template = new JdbcTemplate(dataSource); return template.query(sql, new TargetGroupDataRowMapper(), companyId); } private static final void removeCompanyMarker(final int companyId, final DataSource dataSource) { final String sql = "UPDATE company_info_tbl SET cvalue = 'false', timestamp = CURRENT_TIMESTAMP, description = 'Migration done' WHERE cname = ? and company_id = ?"; final JdbcTemplate template = new JdbcTemplate(dataSource); template.update(sql, ConfigValue.MigrateTargetGroupsOnStartup.toString(), companyId); } }