package com.cedarsoftware.ncube;
import com.cedarsoftware.ncube.formatters.JsonFormatter;
import com.cedarsoftware.ncube.util.CdnClassLoader;
import com.cedarsoftware.util.IOUtilities;
import com.cedarsoftware.util.StringUtilities;
import com.cedarsoftware.util.UniqueIdGenerator;
import com.cedarsoftware.util.io.JsonObject;
import com.cedarsoftware.util.io.JsonReader;
import com.cedarsoftware.util.io.JsonWriter;
import groovy.lang.GroovyClassLoader;
import ncube.grv.method.NCubeGroovyController;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
/**
* This class manages a list of NCubes. This class is referenced
* by NCube in one place - when it joins to other cubes, it consults
* the NCubeManager to find the joined NCube.
* <p/>
* This class takes care of creating, loading, updating, releasing,
* and deleting NCubes. It also allows you to get a list of NCubes
* matching a wildcard (SQL Like) string.
*
* @author John DeRegnaucourt (jdereg@gmail.com)
* <br/>
* Copyright (c) Cedar Software LLC
* <br/><br/>
* 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
* <br/><br/>
* http://www.apache.org/licenses/LICENSE-2.0
* <br/><br/>
* 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.
*/
public class NCubeManager
{
private static final Map<String, NCube> cubeList = new ConcurrentHashMap<>();
private static final Log LOG = LogFactory.getLog(NCubeManager.class);
private static Map<String, Map<String, Advice>> advices = new LinkedHashMap<>();
private static Map<String, GroovyClassLoader> urlClassLoaders = new ConcurrentHashMap<>();
static
{
urlClassLoaders.put("file", new CdnClassLoader(NCubeManager.class.getClassLoader(), true, true));
}
/**
* @param name String name of an NCube.
* @return NCube instance with the given name. Please note
* that the cube must be loaded first before calling this.
*/
public static NCube getCube(String name, String version)
{
return cubeList.get(makeCacheKey(name, version));
}
static String makeCacheKey(String name, String version)
{
return name + '.' + version;
}
public static void setBaseResourceUrls(List<String> urls, String version)
{
if (urlClassLoaders.containsKey(version))
{
LOG.warn("Adding additional resource URLs, n-cube version: " + version + ", urls: " + urls);
}
GroovyClassLoader urlClassLoader = new CdnClassLoader(NCubeManager.class.getClassLoader(), true, true);
addUrlsToClassLoader(urls, urlClassLoader);
urlClassLoaders.put(version, urlClassLoader);
}
@Deprecated
public static void setUrlClassLoader(List<String> urls, String version)
{
setBaseResourceUrls(urls, version);
}
private static void addUrlsToClassLoader(List<String> urls, GroovyClassLoader urlClassLoader)
{
for (String url : urls)
{
try
{
if (!url.endsWith("/"))
{
url += "/";
}
urlClassLoader.addURL(new URL(url));
}
catch (Exception e)
{
throw new IllegalArgumentException("A URL in List of URLs is malformed: " + url, e);
}
}
}
public static URLClassLoader getUrlClassLoader(String version)
{
return urlClassLoaders.get(version);
}
/**
* Add a cube to the internal map of available cubes.
*
* @param ncube NCube to add to the list.
*/
static void addCube(NCube ncube, String version)
{
synchronized (cubeList)
{
ncube.setVersion(version);
cubeList.put(makeCacheKey(ncube.getName(), version), ncube);
for (Map.Entry<String, Map<String, Advice>> entry : advices.entrySet())
{
String regex = StringUtilities.wildcardToRegexString(entry.getKey());
Axis axis = ncube.getAxis("method");
if (axis != null)
{ // Controller methods
for (Column column : axis.getColumnsWithoutDefault())
{
String method = column.getValue().toString();
String classMethod = ncube.getName() + '.' + method + "()";
if (classMethod.matches(regex))
{
for (Advice advice : entry.getValue().values())
{
ncube.addAdvice(advice, method);
}
}
}
}
else
{ // Expressions
String classMethod = ncube.getName() + ".run()";
if (classMethod.matches(regex))
{
for (Advice advice : entry.getValue().values())
{
ncube.addAdvice(advice, "run");
}
}
}
}
}
}
/**
* @return Map<String, NCube> of all NCubes that are currently
* loaded (cached) in memory. A copy of the internal cache
* is returned.
*/
public static Map<String, NCube> getCachedNCubes()
{
synchronized (cubeList)
{
return new TreeMap<>(cubeList);
}
}
/**
* Used for testing.
*/
public static void clearCubeList()
{
synchronized (cubeList)
{
cubeList.clear();
GroovyBase.clearCache();
NCubeGroovyController.clearCache();
for (Map.Entry<String, GroovyClassLoader> entry : urlClassLoaders.entrySet())
{
GroovyClassLoader classLoader = entry.getValue();
classLoader.clearCache(); // free up Class cache
}
advices.clear();
}
}
/**
* Associate Advice to all n-cubes that match the passed in regular expression.
*/
public static void addAdvice(String wildcard, Advice advice)
{
synchronized (cubeList)
{
Map<String, Advice> current = advices.get(wildcard);
if (current == null)
{
current = new LinkedHashMap<>();
advices.put(wildcard, current);
}
current.put(advice.getName(), advice);
String regex = StringUtilities.wildcardToRegexString(wildcard);
for (NCube ncube : cubeList.values())
{
Axis axis = ncube.getAxis("method");
if (axis != null)
{ // Controller methods
for (Column column : axis.getColumnsWithoutDefault())
{
String method = column.getValue().toString();
String classMethod = ncube.getName() + '.' + method + "()";
if (classMethod.matches(regex))
{
ncube.addAdvice(advice, method);
}
}
}
else
{ // Expressions
String classMethod = ncube.getName() + ".run()";
if (classMethod.matches(regex))
{
ncube.addAdvice(advice, "run");
}
}
}
}
}
private static void jdbcCleanup(PreparedStatement stmt)
{
if (stmt != null)
{
try
{
stmt.close();
}
catch (SQLException e)
{
LOG.error("Error closing JDBC Statement", e);
}
}
}
private static void validateConnection(Connection c)
{
try
{
if (c == null)
{
throw new IllegalArgumentException("Connection cannot be null");
}
else if (c.isClosed())
{
throw new IllegalStateException("Connection already closed.");
}
}
catch (SQLException e)
{
throw new IllegalStateException("Error closing connection.", e);
}
}
public static void validateApp(String app)
{
if (StringUtilities.isEmpty(app))
{
throw new IllegalArgumentException("App cannot be null or empty");
}
}
public static void validateCubeName(String cubeName)
{
if (StringUtilities.isEmpty(cubeName))
{
throw new IllegalArgumentException("n-cube name cannot be null or empty");
}
Matcher m = Regexes.validCubeName.matcher(cubeName);
if (m.find())
{
if (cubeName.equals(m.group(0)))
{
return;
}
}
throw new IllegalArgumentException("n-cube name can only contain a-z, A-Z, 0-9, :, ., _, -, #, and |");
}
public static void validateVersion(String version)
{
if (StringUtilities.isEmpty(version))
{
throw new IllegalArgumentException("n-cube version cannot be null or empty");
}
Matcher m = Regexes.validVersion.matcher(version);
if (m.find())
{
return;
}
throw new IllegalArgumentException("n-cube version must follow the form n.n.n where n is a number 0 or greater. The numbers stand for major.minor.revision");
}
public static void validateStatus(String status)
{
if ("RELEASE".equals(status) || "SNAPSHOT".equals(status))
{
return;
}
throw new IllegalArgumentException("n-cube status must be RELEASE or SNAPSHOT");
}
/**
* Load an NCube from the database (any joined sub-cubes will also be loaded).
*
* @return NCube that matches, or null if not found.
*/
public static NCube loadCube(Connection connection, String app, String name, String version, String status, Date sysDate)
{
validate(connection, app, version);
validateCubeName(name);
if (sysDate == null)
{
sysDate = new Date();
}
synchronized (cubeList)
{
// This is Java 7 specific, but will autoclose the statement
// when it leaves the try statement. If you want to change to this
// let me know and I'll change the other instances.
try (PreparedStatement stmt = connection.prepareStatement("SELECT cube_value_bin FROM n_cube WHERE n_cube_nm = ? AND app_cd = ? AND sys_effective_dt <= ? AND (sys_expiration_dt IS NULL OR sys_expiration_dt >= ?) AND version_no_cd = ? AND status_cd = ?"))
{
java.sql.Date systemDate = new java.sql.Date(sysDate.getTime());
stmt.setString(1, name);
stmt.setString(2, app);
stmt.setDate(3, systemDate);
stmt.setDate(4, systemDate);
stmt.setString(5, version);
stmt.setString(6, status);
ResultSet rs = stmt.executeQuery();
if (rs.next())
{
byte[] jsonBytes = rs.getBytes("cube_value_bin");
String json = new String(jsonBytes, "UTF-8");
NCube ncube = ncubeFromJson(json);
if (rs.next())
{
throw new IllegalStateException("More than one NCube matching name: " + ncube.getName() + ", app: " + app + ", version: " + version + ", status: " + status + ", sysDate: " + sysDate);
}
addCube(ncube, version);
Set<String> subCubeList = ncube.getReferencedCubeNames();
for (String cubeName : subCubeList)
{
final String cacheKey = makeCacheKey(cubeName, version);
if (!cubeList.containsKey(cacheKey))
{
loadCube(connection, app, cubeName, version, status, sysDate);
}
}
return ncube;
}
return null; // Indicates not found
}
catch (IllegalStateException e)
{
throw e;
}
catch (Exception e)
{
String s = "Unable to load nNCube: " + name + ", app: " + app + ", version: " + version + ", status: " + status + ", sysDate: " + sysDate + " from database";
LOG.error(s, e);
throw new RuntimeException(s, e);
}
}
}
/**
* Load an NCube from the database (any joined sub-cubes will also be loaded).
*
* @return NCube that matches, or null if not found.
*/
public static boolean doesCubeExist(Connection connection, String app, String name, String version, String status, Date sysDate)
{
validate(connection, app, version);
validateCubeName(name);
validateStatus(status);
if (sysDate == null)
{
sysDate = new Date();
}
synchronized (cubeList)
{
PreparedStatement stmt = null;
try
{
java.sql.Date systemDate = new java.sql.Date(sysDate.getTime());
stmt = connection.prepareStatement("SELECT n_cube_nm FROM n_cube WHERE n_cube_nm = ? AND app_cd = ? AND sys_effective_dt <= ? AND (sys_expiration_dt IS NULL OR sys_expiration_dt >= ?) AND version_no_cd = ? AND status_cd = ?");
stmt.setString(1, name);
stmt.setString(2, app);
stmt.setDate(3, systemDate);
stmt.setDate(4, systemDate);
stmt.setString(5, version);
stmt.setString(6, status);
ResultSet rs = stmt.executeQuery();
return rs.next();
}
catch (Exception e)
{
String s = "Error finding cube: " + name + ", app: " + app + ", version: " + version + ", status: " + status + ", sysDate: " + sysDate + " from database";
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt);
}
}
}
/**
* Retrieve all cube names that are deeply referenced by the named app, cube (name), version, and status.
*/
public static void getReferencedCubeNames(Connection connection, String app, String name, String version, String status, Date sysDate, Set<String> refs)
{
validate(connection, app, version);
validateCubeName(name);
validateStatus(status);
if (sysDate == null)
{
sysDate = new Date();
}
if (refs == null)
{
throw new IllegalArgumentException("null passed in for Set to hold referenced n-cube names");
}
PreparedStatement stmt = null;
try
{
java.sql.Date systemDate = new java.sql.Date(sysDate.getTime());
stmt = connection.prepareStatement("SELECT cube_value_bin FROM n_cube WHERE n_cube_nm = ? AND app_cd = ? AND sys_effective_dt <= ? AND (sys_expiration_dt IS NULL OR sys_expiration_dt >= ?) AND version_no_cd = ? AND status_cd = ?");
stmt.setString(1, name);
stmt.setString(2, app);
stmt.setDate(3, systemDate);
stmt.setDate(4, systemDate);
stmt.setString(5, version);
stmt.setString(6, status);
ResultSet rs = stmt.executeQuery();
if (rs.next())
{
byte[] jsonBytes = rs.getBytes("cube_value_bin");
String json = new String(jsonBytes, "UTF-8");
NCube ncube = ncubeFromJson(json);
if (rs.next())
{
throw new IllegalStateException("More than one NCube matching name: " + ncube.getName() + ", app: " + app + ", version: " + version + ", status: " + status + ", sysDate: " + sysDate);
}
Set<String> subCubeList = ncube.getReferencedCubeNames();
refs.addAll(subCubeList);
for (String cubeName : subCubeList)
{
if (!refs.contains(cubeName))
{
getReferencedCubeNames(connection, app, cubeName, version, status, sysDate, refs);
}
}
}
}
catch (IllegalStateException e)
{
throw e;
}
catch (Exception e)
{
String s = "Unable to load nNCube: " + name + ", app: " + app + ", version: " + version + ", status: " + status + ", sysDate: " + sysDate + " from database";
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt);
}
}
/**
* Retrieve all n-cubes that have a name that matches the SQL like statement, within the specified app, status,
* version, and system date.
*/
public static Object[] getNCubes(Connection connection, String app, String version, String status, String sqlLike, Date sysDate)
{
validate(connection, app, version);
validateStatus(status);
if (sqlLike == null)
{
sqlLike = "%";
}
if (sysDate == null)
{
sysDate = new Date();
}
PreparedStatement stmt = null;
try
{
java.sql.Date systemDate = new java.sql.Date(sysDate.getTime());
stmt = connection.prepareStatement("SELECT n_cube_id, n_cube_nm, notes_bin, version_no_cd, status_cd, app_cd, create_dt, update_dt, " +
"create_hid, update_hid, sys_effective_dt, sys_expiration_dt, business_effective_dt, business_expiration_dt FROM n_cube WHERE n_cube_nm LIKE ? AND app_cd = ? AND version_no_cd = ? AND status_cd = ? AND sys_effective_dt <= ? AND (sys_expiration_dt IS NULL OR sys_expiration_dt >= ?)");
stmt.setString(1, sqlLike);
stmt.setString(2, app);
stmt.setString(3, version);
stmt.setString(4, status);
stmt.setDate(5, systemDate);
stmt.setDate(6, systemDate);
ResultSet rs = stmt.executeQuery();
List<NCubeInfoDto> records = new ArrayList<NCubeInfoDto>();
while (rs.next())
{
NCubeInfoDto dto = new NCubeInfoDto();
dto.id = Long.toString(rs.getLong("n_cube_id"));
dto.name = rs.getString("n_cube_nm");
byte[] notes = rs.getBytes("notes_bin");
dto.notes = new String(notes == null ? "".getBytes() : notes, "UTF-8");
dto.version = rs.getString("version_no_cd");
dto.status = rs.getString("status_cd");
dto.app = rs.getString("app_cd");
dto.createDate = rs.getDate("create_dt");
dto.updateDate = rs.getDate("update_dt");
dto.createHid = rs.getString("create_hid");
dto.updateHid = rs.getString("update_hid");
dto.sysEffDate = rs.getDate("sys_effective_dt");
dto.sysEndDate = rs.getDate("sys_expiration_dt");
dto.bizEffDate = rs.getDate("business_effective_dt");
dto.bizExpDate = rs.getDate("business_expiration_dt");
records.add(dto);
}
return records.toArray();
}
catch (Exception e)
{
String s = "Unable to fetch NCubes matching '" + sqlLike + "' from database";
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt);
}
}
/**
* Duplicate the specified n-cube, given it the new name, and the same app, version, status as the source n-cube.
*/
public static void duplicate(Connection connection, String newName, String name, String newApp, String app, String newVersion, String version, String status, Date sysDate)
{
NCube ncube = loadCube(connection, app, name, version, status, sysDate);
NCube copy = ncube.duplicate(newName);
createCube(connection, newApp, copy, newVersion);
String json = getTestData(connection, app, name, version, sysDate);
updateTestData(connection, newApp, newName, newVersion, json);
String notes = getNotes(connection, app, name, version, sysDate);
updateNotes(connection, newApp, newName, newVersion, notes);
}
/**
* Return an array [] of Strings containing all unique App names.
*/
public static Object[] getAppNames(Connection connection, Date sysDate)
{
validateConnection(connection);
if (sysDate == null)
{
sysDate = new Date();
}
PreparedStatement stmt = null;
try
{
java.sql.Date systemDate = new java.sql.Date(sysDate.getTime());
stmt = connection.prepareStatement("SELECT DISTINCT app_cd FROM n_cube WHERE sys_effective_dt <= ? AND (sys_expiration_dt IS NULL OR sys_expiration_dt >= ?)");
stmt.setDate(1, systemDate);
stmt.setDate(2, systemDate);
ResultSet rs = stmt.executeQuery();
List<String> records = new ArrayList<String>();
while (rs.next())
{
records.add(rs.getString(1));
}
Collections.sort(records);
return records.toArray();
}
catch (Exception e)
{
String s = "Unable to fetch all ncube app names from database";
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt);
}
}
/**
* Return an array [] of Strings containing all unique App names.
*/
public static Object[] getAppVersions(Connection connection, String app, String status, Date sysDate)
{
validateConnection(connection);
validateApp(app);
validateStatus(status);
if (sysDate == null)
{
sysDate = new Date();
}
PreparedStatement stmt = null;
try
{
java.sql.Date systemDate = new java.sql.Date(sysDate.getTime());
stmt = connection.prepareStatement("SELECT DISTINCT version_no_cd FROM n_cube WHERE app_cd = ? and status_cd = ? AND sys_effective_dt <= ? AND (sys_expiration_dt IS NULL OR sys_expiration_dt >= ?)");
stmt.setString(1, app);
stmt.setString(2, status);
stmt.setDate(3, systemDate);
stmt.setDate(4, systemDate);
ResultSet rs = stmt.executeQuery();
List<String> records = new ArrayList<String>();
while (rs.next())
{
records.add(rs.getString(1));
}
Collections.sort(records); // May need to enhance to ensure 2.19.1 comes after 2.2.1
return records.toArray();
}
catch (Exception e)
{
String s = "Unable to fetch all ncube app versions from database";
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt);
}
}
/**
* Update the passed in NCube. Only SNAPSHOT ncubes can be updated.
*
* @param connection JDBC connection
* @param ncube NCube to be updated.
* @return boolean true on success, false otherwise
*/
public static boolean updateCube(Connection connection, String app, NCube ncube, String version)
{
validate(connection, app, version);
if (ncube == null)
{
throw new IllegalArgumentException("NCube cannot be null for updating");
}
synchronized (cubeList)
{
PreparedStatement stmt = null;
try
{
stmt = connection.prepareStatement("UPDATE n_cube SET cube_value_bin=?, update_dt=? WHERE app_cd = ? AND n_cube_nm = ? AND version_no_cd = ? AND status_cd = '" + ReleaseStatus.SNAPSHOT + "'");
stmt.setBytes(1, new JsonFormatter().format(ncube).getBytes("UTF-8"));
stmt.setDate(2, new java.sql.Date(System.currentTimeMillis()));
stmt.setString(3, app);
stmt.setString(4, ncube.getName());
stmt.setString(5, version);
int count = stmt.executeUpdate();
if (count != 1)
{
throw new IllegalStateException("Only one (1) row should be updated.");
}
ncube.setVersion(version);
return true;
}
catch (IllegalStateException e)
{
throw e;
}
catch (Exception e)
{
String s = "Unable to update NCube: " + ncube.getName() + ", app: " + app + ", version: " + version;
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt);
}
}
}
/**
* Persist the passed in NCube
*
* @param connection JDBC connection
* @param ncube NCube to be persisted
*/
public static void createCube(Connection connection, String app, NCube ncube, String version)
{
validate(connection, app, version);
if (ncube == null)
{
throw new IllegalArgumentException("NCube cannot be null when creating a new n-cube");
}
validateCubeName(ncube.getName());
synchronized (cubeList)
{
PreparedStatement stmt = null;
try
{
stmt = connection.prepareStatement("SELECT n_cube_id AS \"id\" FROM n_cube WHERE app_cd = ? AND n_cube_nm = ? AND version_no_cd = ?");
stmt.setString(1, app);
stmt.setString(2, ncube.getName());
stmt.setString(3, version);
ResultSet rs = stmt.executeQuery();
if (rs.next())
{ // NCube with same name and version number already exists.
throw new IllegalStateException("NCube '" + ncube.getName() + "' (" + version + ") already exists.");
}
else
{ // Do INSERT
stmt.close();
stmt = connection.prepareStatement("INSERT INTO n_cube (n_cube_id, app_cd, n_cube_nm, cube_value_bin, version_no_cd, create_dt, sys_effective_dt) VALUES (?, ?, ?, ?, ?, ?, ?)");
stmt.setLong(1, UniqueIdGenerator.getUniqueId());
stmt.setString(2, app);
stmt.setString(3, ncube.getName());
String json = new JsonFormatter().format(ncube);
// String json = JsonWriter.objectToJson(ncube);
stmt.setBytes(4, json.getBytes("UTF-8"));
stmt.setString(5, version);
java.sql.Date now = new java.sql.Date(System.currentTimeMillis());
stmt.setDate(6, now);
stmt.setDate(7, now);
int rowCount = stmt.executeUpdate();
if (rowCount != 1)
{
throw new IllegalStateException("error saving new NCube: " + ncube.getName() + "', app: " + app + ", version: " + version + " (" + rowCount + " rows inserted, should be 1)");
}
}
addCube(ncube, version);
}
catch (IllegalStateException e)
{
throw e;
}
catch (Exception e)
{
String s = "Unable to save NCube: " + ncube.getName() + ", app: " + app + ", version: " + version + " to database";
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt);
}
}
}
/**
* Move ncubes matching the passed in version and APP_CD from SNAPSHOT to RELEASE
* state. All ncubes move version at the same time. This is by design so that the
* cube join commands do not need to mess with determining what ncube versions
* they join with.
*
* @param connection JDBC connection
* @param version String version to move from SNAPSHOT to RELEASE
* @return int count of ncubes that were released
*/
public static int releaseCubes(Connection connection, String app, String version)
{
validate(connection, app, version);
synchronized (cubeList)
{
PreparedStatement stmt1 = null;
PreparedStatement stmt2 = null;
try
{
stmt1 = connection.prepareStatement("SELECT n_cube_id FROM n_cube WHERE app_cd = ? AND version_no_cd = ? AND status_cd = '" + ReleaseStatus.RELEASE + "'");
stmt1.setString(1, app);
stmt1.setString(2, version);
ResultSet rs = stmt1.executeQuery();
if (rs.next())
{
throw new IllegalStateException("A RELEASE version " + version + " already exists. Have system admin renumber your SNAPSHOT version.");
}
stmt2 = connection.prepareStatement("UPDATE n_cube SET update_dt = ?, status_cd = '" + ReleaseStatus.RELEASE + "' WHERE app_cd = ? AND version_no_cd = ? AND status_cd = '" + ReleaseStatus.SNAPSHOT + "'");
stmt2.setDate(1, new java.sql.Date(System.currentTimeMillis()));
stmt2.setString(2, app);
stmt2.setString(3, version);
return stmt2.executeUpdate();
}
catch (IllegalStateException e)
{
throw e;
}
catch (Exception e)
{
String s = "Unable to release NCubes for app: " + app + ", version: " + version + ", due to an error: " + e.getMessage();
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt1);
jdbcCleanup(stmt2);
}
}
}
/**
* This API creates a SNAPSHOT set of cubes by copying all of
* the RELEASE ncubes that match the oldVersion and app to
* the new version in SNAPSHOT mode. Basically, it duplicates
* an entire set of NCubes and places a new version label on them,
* in SNAPSHOT status.
*/
public static int createSnapshotCubes(Connection connection, String app, String relVersion, String newSnapVer)
{
validate(connection, app, relVersion);
validateVersion(newSnapVer);
if (relVersion.equals(newSnapVer))
{
throw new IllegalArgumentException("New SNAPSHOT version " + relVersion + " cannot be the same as the RELEASE version.");
}
synchronized (cubeList)
{
PreparedStatement stmt0 = null;
PreparedStatement stmt1 = null;
PreparedStatement stmt2 = null;
try
{
stmt0 = connection.prepareStatement("SELECT n_cube_id FROM n_cube WHERE app_cd = ? AND version_no_cd = ?");
stmt0.setString(1, app);
stmt0.setString(2, newSnapVer);
ResultSet rs = stmt0.executeQuery();
if (rs.next())
{
throw new IllegalStateException("New SNAPSHOT Version specified (" + newSnapVer + ") matches an existing version. Specify new SNAPSHOT version that does not exist.");
}
rs.close();
stmt1 = connection.prepareStatement(
"SELECT n_cube_nm, cube_value_bin, create_dt, update_dt, create_hid, update_hid, version_no_cd, status_cd, sys_effective_dt, sys_expiration_dt, business_effective_dt, business_expiration_dt, app_cd, test_data_bin, notes_bin\n" +
"FROM n_cube\n" +
"WHERE app_cd = ? AND version_no_cd = ? AND status_cd = '" + ReleaseStatus.RELEASE + "'"
);
stmt1.setString(1, app);
stmt1.setString(2, relVersion);
rs = stmt1.executeQuery();
stmt2 = connection.prepareStatement(
"INSERT INTO n_cube (n_cube_id, n_cube_nm, cube_value_bin, create_dt, update_dt, create_hid, update_hid, version_no_cd, status_cd, sys_effective_dt, sys_expiration_dt, business_effective_dt, business_expiration_dt, app_cd, test_data_bin, notes_bin)\n" +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
int count = 0;
while (rs.next())
{
count++;
stmt2.setLong(1, UniqueIdGenerator.getUniqueId());
stmt2.setString(2, rs.getString("n_cube_nm"));
stmt2.setBytes(3, rs.getBytes("cube_value_bin"));
stmt2.setDate(4, new java.sql.Date(System.currentTimeMillis()));
stmt2.setDate(5, new java.sql.Date(System.currentTimeMillis()));
stmt2.setString(6, rs.getString("create_hid"));
stmt2.setString(7, rs.getString("update_hid"));
stmt2.setString(8, newSnapVer);
stmt2.setString(9, ReleaseStatus.SNAPSHOT.name());
stmt2.setDate(10, rs.getDate("sys_effective_dt"));
stmt2.setDate(11, rs.getDate("sys_expiration_dt"));
stmt2.setDate(12, rs.getDate("business_effective_dt"));
stmt2.setDate(13, rs.getDate("business_expiration_dt"));
stmt2.setString(14, rs.getString("app_cd"));
stmt2.setBytes(15, rs.getBytes("test_data_bin"));
stmt2.setBytes(16, rs.getBytes("notes_bin"));
stmt2.executeUpdate();
}
return count;
}
catch (IllegalStateException e)
{
throw e;
}
catch (Exception e)
{
String s = "Unable to create SNAPSHOT NCubes for app: " + app + ", version: " + newSnapVer + ", due to an error: " + e.getMessage();
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt0);
jdbcCleanup(stmt1);
jdbcCleanup(stmt2);
}
}
}
private static void validate(Connection connection, String app, String relVersion)
{
validateConnection(connection);
validateApp(app);
validateVersion(relVersion);
}
/**
* Change the SNAPSHOT version value.
*/
public static void changeVersionValue(Connection connection, String app, String currVersion, String newSnapVer)
{
validate(connection, app, currVersion);
validateVersion(newSnapVer);
synchronized (cubeList)
{
PreparedStatement stmt1 = null;
PreparedStatement stmt2 = null;
try
{
stmt1 = connection.prepareStatement("SELECT n_cube_id FROM n_cube WHERE app_cd = ? AND version_no_cd = ? AND status_cd = '" + ReleaseStatus.RELEASE + "'");
stmt1.setString(1, app);
stmt1.setString(2, newSnapVer);
ResultSet rs = stmt1.executeQuery();
if (rs.next())
{
throw new IllegalStateException("RELEASE n-cubes found with version " + newSnapVer + ". Choose a different SNAPSHOT version.");
}
stmt2 = connection.prepareStatement("UPDATE n_cube SET update_dt = ?, version_no_cd = ? WHERE app_cd = ? AND version_no_cd = ? AND status_cd = '" + ReleaseStatus.SNAPSHOT + "'");
stmt2.setDate(1, new java.sql.Date(System.currentTimeMillis()));
stmt2.setString(2, newSnapVer);
stmt2.setString(3, app);
stmt2.setString(4, currVersion);
int count = stmt2.executeUpdate();
if (count < 1)
{
throw new IllegalStateException("No SNAPSHOT n-cubes found with version " + currVersion + ", therefore nothing changed.");
}
}
catch (IllegalStateException e)
{
throw e;
}
catch (Exception e)
{
String s = "Unable to change SNAPSHOT version from " + currVersion + " to " + newSnapVer + " for app: " + app + ", due to an error: " + e.getMessage();
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt1);
jdbcCleanup(stmt2);
}
}
}
public static boolean renameCube(Connection connection, String oldName, String newName, String app, String version)
{
validate(connection, app, version);
validateCubeName(oldName);
validateCubeName(newName);
if (oldName.equalsIgnoreCase(newName))
{
throw new IllegalArgumentException("Old name cannot be the same as the new name, name: " + oldName);
}
synchronized (cubeList)
{
PreparedStatement stmt1 = null;
try
{
stmt1 = connection.prepareStatement("UPDATE n_cube SET n_cube_nm = ? WHERE app_cd = ? AND version_no_cd = ? AND n_cube_nm = ? AND status_cd = '" + ReleaseStatus.SNAPSHOT + "'");
stmt1.setString(1, newName);
stmt1.setString(2, app);
stmt1.setString(3, version);
stmt1.setString(4, oldName);
int count = stmt1.executeUpdate();
if (count < 1)
{
throw new IllegalArgumentException("No n-cube found to rename, for app:" + app + ", version: " + version + ", original name: " + oldName);
}
cubeList.remove(makeCacheKey(oldName, version));
return true;
}
catch (IllegalStateException e)
{
throw e;
}
catch (Exception e)
{
String s = "Unable to rename n-cube due to an error: " + e.getMessage();
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt1);
}
}
}
/**
* Delete the named NCube from the database
*
* @param connection JDBC connection
* @param name NCube to be deleted
*/
public static boolean deleteCube(Connection connection, String app, String name, String version, boolean allowDelete)
{
validate(connection, app, version);
validateCubeName(name);
synchronized (cubeList)
{
PreparedStatement stmt = null;
try
{
if (allowDelete)
{
stmt = connection.prepareStatement("DELETE FROM n_cube WHERE app_cd = ? AND n_cube_nm = ? AND version_no_cd = ?");
}
else
{
stmt = connection.prepareStatement("DELETE FROM n_cube WHERE app_cd = ? AND n_cube_nm = ? AND version_no_cd = ? AND status_cd = '" + ReleaseStatus.SNAPSHOT + "'");
}
stmt.setString(1, app);
stmt.setString(2, name);
stmt.setString(3, version);
int rows = stmt.executeUpdate();
if (rows > 0)
{
cubeList.remove(makeCacheKey(name, version));
return true;
}
return false;
}
catch (Exception e)
{
String s = "Unable to delete NCube: " + name + ", app: " + app + ", version: " + version + " from database: " + e.getMessage();
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt);
}
}
}
/**
* Update the notes associated to an NCube
*
* @return true if the update succeeds, false otherwise
*/
public static boolean updateNotes(Connection connection, String app, String name, String version, String notes)
{
validate(connection, app, version);
validateCubeName(name);
synchronized (cubeList)
{
PreparedStatement stmt = null;
try
{
stmt = connection.prepareStatement("UPDATE n_cube SET notes_bin = ?, update_dt = ? WHERE app_cd = ? AND n_cube_nm = ? AND version_no_cd = ?");
stmt.setBytes(1, notes == null ? null : notes.getBytes("UTF-8"));
stmt.setDate(2, new java.sql.Date(System.currentTimeMillis()));
stmt.setString(3, app);
stmt.setString(4, name);
stmt.setString(5, version);
int count = stmt.executeUpdate();
if (count > 1)
{
throw new IllegalStateException("Only one (1) row's notes should be updated.");
}
if (count == 0)
{
throw new IllegalStateException("No NCube matching app: " + app + ", name: " + name + ", version: " + version);
}
return true;
}
catch (IllegalStateException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException("Unable to update notes for NCube: " + name + ", app: " + app + ", version: " + version, e);
}
finally
{
jdbcCleanup(stmt);
}
}
}
/**
* Get the notes associated to an NCube
*
* @return String notes.
*/
public static String getNotes(Connection connection, String app, String name, String version, Date sysDate)
{
validate(connection, app, version);
validateCubeName(name);
if (sysDate == null)
{
sysDate = new Date();
}
PreparedStatement stmt = null;
try
{
java.sql.Date systemDate = new java.sql.Date(sysDate.getTime());
stmt = connection.prepareStatement("SELECT notes_bin FROM n_cube WHERE app_cd = ? AND n_cube_nm = ? AND version_no_cd = ? AND sys_effective_dt <= ? AND (sys_expiration_dt IS NULL OR sys_expiration_dt >= ?)");
stmt.setString(1, app);
stmt.setString(2, name);
stmt.setString(3, version);
stmt.setDate(4, systemDate);
stmt.setDate(5, systemDate);
ResultSet rs = stmt.executeQuery();
if (rs.next())
{
byte[] notes = rs.getBytes("notes_bin");
return new String(notes == null ? "".getBytes() : notes, "UTF-8");
}
throw new IllegalArgumentException("No NCube matching passed in parameters.");
}
catch (IllegalArgumentException e)
{
throw e;
}
catch (Exception e)
{
String s = "Unable to fetch notes for NCube: " + name + ", app: " + app + ", version: " + version;
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt);
}
}
/**
* Update the test data associated to an NCube
*
* @return true if the update succeeds, false otherwise
*/
public static boolean updateTestData(Connection connection, String app, String name, String version, String testData)
{
validate(connection, app, version);
validateCubeName(name);
synchronized (cubeList)
{
PreparedStatement stmt = null;
try
{
stmt = connection.prepareStatement("UPDATE n_cube SET test_data_bin=?, update_dt=? WHERE app_cd = ? AND n_cube_nm = ? AND version_no_cd = ? AND status_cd = '" + ReleaseStatus.SNAPSHOT + "'");
stmt.setBytes(1, testData == null ? null : testData.getBytes("UTF-8"));
stmt.setDate(2, new java.sql.Date(System.currentTimeMillis()));
stmt.setString(3, app);
stmt.setString(4, name);
stmt.setString(5, version);
int count = stmt.executeUpdate();
if (count > 1)
{
throw new IllegalStateException("Only one (1) row's test data should be updated.");
}
if (count == 0)
{
throw new IllegalStateException("No NCube matching app: " + app + ", name: " + name + ", version: " + version);
}
return true;
}
catch (IllegalStateException e)
{
throw e;
}
catch (Exception e)
{
String s = "Unable to update test data for NCube: " + name + ", app: " + app + ", version: " + version;
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt);
}
}
}
/**
* Get the Test Data associated to the NCube.
*
* @return String serialized JSON test data. Use JsonReader to turn it back into
* Java objects.
*/
public static String getTestData(Connection connection, String app, String name, String version, Date sysDate)
{
validate(connection, app, version);
validateCubeName(name);
if (sysDate == null)
{
sysDate = new Date();
}
PreparedStatement stmt = null;
try
{
java.sql.Date systemDate = new java.sql.Date(sysDate.getTime());
stmt = connection.prepareStatement("SELECT test_data_bin FROM n_cube WHERE app_cd = ? AND n_cube_nm = ? AND version_no_cd = ? AND sys_effective_dt <= ? AND (sys_expiration_dt IS NULL OR sys_expiration_dt >= ?)");
stmt.setString(1, app);
stmt.setString(2, name);
stmt.setString(3, version);
stmt.setDate(4, systemDate);
stmt.setDate(5, systemDate);
ResultSet rs = stmt.executeQuery();
if (rs.next())
{
byte[] testData = rs.getBytes("test_data_bin");
return new String(testData == null ? "".getBytes() : testData, "UTF-8");
}
throw new IllegalArgumentException("No NCube matching passed in parameters.");
}
catch (IllegalArgumentException e)
{
throw e;
}
catch (Exception e)
{
String s = "Unable to fetch test data for NCube: " + name + ", app: " + app + ", version: " + version;
LOG.error(s, e);
throw new RuntimeException(s, e);
}
finally
{
jdbcCleanup(stmt);
}
}
private static String getResourceAsString(String name) throws IOException
{
ByteArrayOutputStream out = new ByteArrayOutputStream(8192);
URL url = NCubeManager.class.getResource("/" + name);
IOUtilities.transfer(new File(url.getFile()), out);
return new String(out.toByteArray(), "UTF-8");
}
public static NCube getNCubeFromResource(String name)
{
try
{
String json = getResourceAsString(name);
NCube ncube = ncubeFromJson(json);
addCube(ncube, "file");
return ncube;
}
catch (IOException e)
{
String s = "Failed to load ncube from resource: " + name;
LOG.error(s, e);
throw new RuntimeException(s, e);
}
}
/**
* Still used in getNCubesFromResource
*/
private static JsonObject getJsonObjectFromResource(String name) throws IOException
{
JsonReader reader = null;
try
{
URL url = NCubeManager.class.getResource("/" + name);
File jsonFile = new File(url.getFile());
InputStream in = new BufferedInputStream(new FileInputStream(jsonFile));
reader = new JsonReader(in, true);
return (JsonObject) reader.readObject();
}
finally
{
IOUtilities.close(reader);
}
}
public static List<NCube> getNCubesFromResource(String name)
{
String lastSuccessful = "";
try
{
JsonObject ncubes = getJsonObjectFromResource(name);
Object[] cubes = ncubes.getArray();
List<NCube> cubeList = new ArrayList<>(cubes.length);
for (Object cube : cubes)
{
JsonObject ncube = (JsonObject) cube;
String json = JsonWriter.objectToJson(ncube);
NCube nCube = NCube.fromSimpleJson(json);
addCube(nCube, "file");
lastSuccessful = nCube.getName();
cubeList.add(nCube);
}
return cubeList;
}
catch (Exception e)
{
String s = "Failed to load ncubes from resource: " + name + ", last successful cube: " + lastSuccessful;
LOG.warn(s);
throw new RuntimeException(s, e);
}
}
static NCube ncubeFromJson(String json) throws IOException
{
try
{
return NCube.fromSimpleJson(json);
}
catch (Exception e)
{
LOG.error("Unable to load n-cube from simple JSON format. Trying in serialized JSON format.", e);
return (NCube) JsonReader.jsonToJava(json);
}
}
}