All Downloads are FREE. Search and download functionalities are using the official Maven repository.

marytts.util.MaryCache Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2009 DFKI GmbH.
 * All Rights Reserved.  Use is subject to license terms.
 *
 * This file is part of MARY TTS.
 *
 * MARY TTS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 *
 */

package marytts.util;

import java.io.File;
import java.net.MalformedURLException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import marytts.server.MaryProperties;

/**
 * @author marc
 * 
 */
public class MaryCache {
	private static MaryCache maryCache;

	/**
	 * Try to get the MaryCache object. This will either return the previously created MaryCache, or if none exists, it will try
	 * to create one.
	 * 
	 * @see #haveCache if you just want to check if the cache exists.
	 * 
	 *      To the extent possible this method gives the no-throw guarantee: if the cache cannot be created, null will be returned
	 *      and any exception will be logged.
	 * @return the MaryCache singleton object, or null if none could be created.
	 */
	public static MaryCache getCache() {
		if (maryCache == null) {
			try {
				File targetFile = new File(MaryProperties.getFilename("cache.file", "maryCache"));
				File directory = targetFile.getParentFile();
				if (!directory.isDirectory()) {
					directory.mkdirs();
				}
				maryCache = new MaryCache(targetFile, MaryProperties.getBoolean("cache.clearOnStart", false));
			} catch (Exception e) {
				MaryUtils.getLogger(MaryCache.class).warn("Cannot set up cache", e);
			}
		}
		return maryCache;
	}

	/**
	 * Indicate whether there is a MaryCache currently available.
	 * 
	 * @return true if there is a MaryCache, false otherwise.
	 */
	public static boolean haveCache() {
		return maryCache != null;
	}

	// //////////////////////////// non-static code /////////////////////////////

	private Connection connection;

	/**
	 * Create a MaryCache with the given file prefix. This constructor is public only for tests; it should not normally be called.
	 * User code should call {@link #getCache()} instead. TODO: Find a more elegant way to create a custom MaryCache from test
	 * code.
	 * 
	 * @param cacheFile
	 *            the file name prefix with which to create the cache database.
	 * @param clearCache
	 *            if true, clear the cache; if false, keep it.
	 * @throws ClassNotFoundException
	 *             if the HSQL JDBC driver is not in the classpath.
	 * @throws SQLException
	 *             if the database connection cannot be set up
	 */
	public MaryCache(File cacheFile, boolean clearCache) throws ClassNotFoundException, SQLException {
		// Load the HSQL Database Engine JDBC driver
		Class.forName("org.hsqldb.jdbcDriver");
		connection = DriverManager.getConnection("jdbc:hsqldb:" + cacheFile.toURI().toString(), "sa", "");
		boolean mustCreateTable = false;
		if (clearCache) {
			Statement st = connection.createStatement();
			st.executeUpdate("DROP TABLE MARYCACHE IF EXISTS");
			st.close();
			mustCreateTable = true;
		} else { // don't clear -- check if table exists
			DatabaseMetaData dbInfo = connection.getMetaData();
			ResultSet rs = dbInfo.getTables(null, null, "MARYCACHE", new String[] { "TABLE" });
			if (rs.next()) {
				// table exists
			} else {
				mustCreateTable = true;
			}
		}
		if (mustCreateTable) {
			String query = "CREATE CACHED TABLE MARYCACHE (id INTEGER IDENTITY, " + "inputtype VARCHAR(50), "
					+ "outputtype VARCHAR(50), " + "locale VARCHAR(10), " + "voice VARCHAR(100), "
					+ "outputparams VARCHAR(1000), " + "style VARCHAR(50), " + "effects VARCHAR(1000), "
					+ "inputtext LONGVARCHAR, " + "outputtext LONGVARCHAR, " + "outputaudio LONGVARBINARY, "
					+ "UNIQUE(inputtype, outputtype, locale, voice, outputparams, style, effects, inputtext)" + ")";
			update(query);
		}
	}

	/**
	 * Carry out an UPDATE SQL command on the database.
	 * 
	 * @param query
	 *            the UPDATE SQL command to carry out
	 * @throws SQLException
	 *             if there is a problem executing the update.
	 */
	private synchronized void update(String query) throws SQLException {
		Statement st = connection.createStatement();
		int ok = st.executeUpdate(query);
		if (ok == -1) {
			throw new SQLException("DB problem with query: " + query);
		}
		st.close();
	}

	/**
	 * Insert a record of a MARY request producing data of type text into the cache. If a record with the same lookup keys (i.e.,
	 * all parameters everything except outputtext) exists already, this call does nothing.
	 * 
	 * @param inputtype
	 *            the request's input type. Must not be null.
	 * @param outputtype
	 *            the request's output type, which must be a text type. Must not be null.
	 * @param locale
	 *            the locale of the request. Must not be null.
	 * @param voice
	 *            the voice of the request. Can be null.
	 * @param inputtext
	 *            the request's input text. Must not be null.
	 * @param outputtext
	 *            the request's output text. Must not be null.
	 * @throws NullPointerException
	 *             if one of the fields is null which must be non-null.
	 * @throws SQLException
	 *             if the record could not be entered into the cache.
	 */
	public void insertText(String inputtype, String outputtype, String locale, String voice, String inputtext, String outputtext)
			throws SQLException {
		insertText(inputtype, outputtype, locale, voice, null, null, null, inputtext, outputtext);
	}

	/**
	 * Insert a record of a MARY request producing data of type text into the cache. If a record with the same lookup keys (i.e.,
	 * all parameters everything except outputtext) exists already, this call does nothing.
	 * 
	 * @param inputtype
	 *            the request's input type. Must not be null.
	 * @param outputtype
	 *            the request's output type, which must be a text type. Must not be null.
	 * @param locale
	 *            the locale of the request. Must not be null.
	 * @param voice
	 *            the voice of the request. Can be null.
	 * @param outputparams
	 *            optionally, any output parameters. Can be null.
	 * @param style
	 *            optionally, any style. Can be null.
	 * @param effects
	 *            optionally, any effects. Can be null.
	 * @param inputtext
	 *            the request's input text. Must not be null.
	 * @param outputtext
	 *            the request's output text. Must not be null.
	 * @throws NullPointerException
	 *             if one of the fields is null which must be non-null.
	 * @throws SQLException
	 *             if the record could not be entered into the cache.
	 */
	public synchronized void insertText(String inputtype, String outputtype, String locale, String voice, String outputparams,
			String style, String effects, String inputtext, String outputtext) throws SQLException {
		if (inputtype == null || outputtype == null || locale == null || voice == null || inputtext == null || outputtext == null) {
			throw new NullPointerException("Null argument");
		}
		// Need to verify, here in the synchronized code, once again that really we don't have this entry already.
		// If we do, we ignore this call.
		if (lookupText(inputtype, outputtype, locale, voice, outputparams, style, effects, inputtext) != null) {
			return;
		}

		String query = "INSERT INTO MARYCACHE (inputtype, outputtype, locale, voice, outputparams, style, effects, inputtext, outputtext) VALUES ('"
				+ inputtype
				+ "','"
				+ outputtype
				+ "','"
				+ locale
				+ "','"
				+ voice
				+ "','"
				+ outputparams
				+ "','"
				+ style
				+ "','"
				+ effects + "',?,?)";

		PreparedStatement st = connection.prepareStatement(query);
		// We set the input and output text separately because they could contain single quote characters
		st.setString(1, inputtext);
		st.setString(2, outputtext);
		st.executeUpdate();
		st.close();
	}

	/**
	 * Insert a record of a MARY request producing data of output type AUDIO into the cache. If a record with the same lookup keys
	 * (i.e., all parameters everything except audio) exists already, this call does nothing.
	 * 
	 * @param inputtype
	 *            the request's input type. Must not be null.
	 * @param locale
	 *            the locale of the request. Must not be null.
	 * @param voice
	 *            the voice of the request. Can be null.
	 * @param inputtext
	 *            the request's input text. Must not be null.
	 * @param audio
	 *            the request's output data. Must not be null.
	 * @throws NullPointerException
	 *             if one of the fields is null which must be non-null.
	 * @throws SQLException
	 *             if the record could not be entered into the cache.
	 */
	public void insertAudio(String inputtype, String locale, String voice, String inputtext, byte[] audio) throws SQLException {
		insertAudio(inputtype, locale, voice, null, null, null, inputtext, audio);
	}

	/**
	 * Insert a record of a MARY request producing data of output type AUDIO into the cache. If a record with the same lookup keys
	 * (i.e., all parameters everything except audio) exists already, this call does nothing.
	 * 
	 * @param inputtype
	 *            the request's input type. Must not be null.
	 * @param locale
	 *            the locale of the request. Must not be null.
	 * @param voice
	 *            the voice of the request. Can be null.
	 * @param outputparams
	 *            optionally, any output parameters. Can be null.
	 * @param style
	 *            optionally, any style. Can be null.
	 * @param effects
	 *            optionally, any effects. Can be null.
	 * @param inputtext
	 *            the request's input text. Must not be null.
	 * @param audio
	 *            the request's output data. Must not be null.
	 * @throws NullPointerException
	 *             if one of the fields is null which must be non-null.
	 * @throws SQLException
	 *             if the record could not be entered into the cache.
	 */
	public synchronized void insertAudio(String inputtype, String locale, String voice, String outputparams, String style,
			String effects, String inputtext, byte[] audio) throws SQLException {
		if (inputtype == null || locale == null || voice == null || inputtext == null) {
			throw new NullPointerException("Null argument");
		}
		// Need to verify, here in the synchronized code, once again that really we don't have this entry already.
		// If we do, we ignore this call.
		if (lookupAudio(inputtype, locale, voice, outputparams, style, effects, inputtext) != null) {
			return;
		}

		String query = "INSERT INTO MARYCACHE (inputtype, outputtype, locale, voice, outputparams, style, effects, inputtext, outputaudio) VALUES('"
				+ inputtype
				+ "','AUDIO','"
				+ locale
				+ "','"
				+ voice
				+ "','"
				+ outputparams
				+ "','"
				+ style
				+ "','"
				+ effects
				+ "',?,?)";

		PreparedStatement st = connection.prepareStatement(query);
		st.setString(1, inputtext);
		st.setBytes(2, audio);
		st.executeUpdate();
		st.close();
	}

	/**
	 * Carry out a lookup in the cache with the given parameters.
	 * 
	 * @param inputtype
	 *            the request's input type. Must not be null.
	 * @param outputtype
	 *            the request's output type, which must be a text type. Must not be null.
	 * @param locale
	 *            the locale of the request. Must not be null.
	 * @param voice
	 *            the voice of the request. Can be null.
	 * @param inputtext
	 *            the request's input text. Must not be null.
	 * @return the output text associated with the with the given record, or null if the cache does not contain a record with
	 *         these keys.
	 * @throws NullPointerException
	 *             if one of the fields is null which must be non-null.
	 * @throws SQLException
	 *             if there is a problem querying the cache.
	 */
	public String lookupText(String inputtype, String outputtype, String locale, String voice, String inputtext)
			throws SQLException {
		return lookupText(inputtype, outputtype, locale, voice, null, null, null, inputtext);
	}

	/**
	 * Carry out a lookup in the cache with the given parameters.
	 * 
	 * @param inputtype
	 *            the request's input type. Must not be null.
	 * @param outputtype
	 *            the request's output type, which must be a text type. Must not be null.
	 * @param locale
	 *            the locale of the request. Must not be null.
	 * @param voice
	 *            the voice of the request. Can be null.
	 * @param outputparams
	 *            optionally, any output parameters. Can be null.
	 * @param style
	 *            optionally, any style. Can be null.
	 * @param effects
	 *            optionally, any effects. Can be null.
	 * @param inputtext
	 *            the request's input text. Must not be null.
	 * @return the output text associated with the with the given record, or null if the cache does not contain a record with
	 *         these keys.
	 * @throws NullPointerException
	 *             if one of the fields is null which must be non-null.
	 * @throws SQLException
	 *             if there is a problem querying the cache.
	 */
	public synchronized String lookupText(String inputtype, String outputtype, String locale, String voice, String outputparams,
			String style, String effects, String inputtext) throws SQLException {
		if (inputtype == null || outputtype == null || locale == null || voice == null || inputtext == null) {
			throw new NullPointerException("Null argument");
		}
		String outputtext = null;
		String query = "SELECT outputtext FROM marycache WHERE inputtype = '" + inputtype + "' AND outputtype = '" + outputtype
				+ "' AND locale = '" + locale + "' AND voice = '" + voice + "' AND outputparams = '" + outputparams
				+ "' AND style = '" + style + "' AND effects = '" + effects + "' AND inputtext = ?";

		PreparedStatement st = connection.prepareStatement(query);
		st.setString(1, inputtext);
		ResultSet results = st.executeQuery();
		if (results.next()) { // we expect only a single result, if any, so no while loop
			outputtext = results.getString(1);
		}
		st.close();
		return outputtext;
	}

	/**
	 * Carry out a lookup in the cache with the given parameters, for a request with output type AUDIO.
	 * 
	 * @param inputtype
	 *            the request's input type. Must not be null.
	 * @param locale
	 *            the locale of the request. Must not be null.
	 * @param voice
	 *            the voice of the request. Can be null.
	 * @param inputtext
	 *            the request's input text. Must not be null.
	 * @return the output text associated with the with the given record, or null if the cache does not contain a record with
	 *         these keys.
	 * @throws NullPointerException
	 *             if one of the fields is null which must be non-null.
	 * @throws SQLException
	 *             if there is a problem querying the cache.
	 */
	public byte[] lookupAudio(String inputtype, String locale, String voice, String inputtext) throws SQLException {
		return lookupAudio(inputtype, locale, voice, null, null, null, inputtext);
	}

	/**
	 * Carry out a lookup in the cache with the given parameters, for a request with output type AUDIO.
	 * 
	 * @param inputtype
	 *            the request's input type. Must not be null.
	 * @param locale
	 *            the locale of the request. Must not be null.
	 * @param voice
	 *            the voice of the request. Can be null.
	 * @param outputparams
	 *            optionally, any output parameters. Can be null.
	 * @param style
	 *            optionally, any style. Can be null.
	 * @param effects
	 *            optionally, any effects. Can be null.
	 * @param inputtext
	 *            the request's input text. Must not be null.
	 * @return the output text associated with the with the given record, or null if the cache does not contain a record with
	 *         these keys.
	 * @throws NullPointerException
	 *             if one of the fields is null which must be non-null.
	 * @throws SQLException
	 *             if there is a problem querying the cache.
	 */
	public synchronized byte[] lookupAudio(String inputtype, String locale, String voice, String outputparams, String style,
			String effects, String inputtext) throws SQLException {
		if (inputtype == null || locale == null || voice == null || inputtext == null) {
			throw new NullPointerException("Null argument");
		}
		byte[] audio = null;
		String query = "Select outputaudio FROM marycache WHERE inputtype = '" + inputtype
				+ "' AND outputtype = 'AUDIO' AND locale = '" + locale + "' AND voice = '" + voice + "' AND outputparams = '"
				+ outputparams + "' AND style = '" + style + "' AND effects = '" + effects + "' AND inputtext = ?";
		PreparedStatement st = connection.prepareStatement(query);
		st.setString(1, inputtext);
		ResultSet results = st.executeQuery();
		if (results.next()) {
			audio = results.getBytes(1);
		}
		return audio;
	}

	/**
	 * Shut down the cache. After this has been called, any further calls to the object will throw exceptions.
	 * 
	 * @throws SQLException
	 *             if there is a problem executing the database SHUTDOWN command.
	 */
	public void shutdown() throws SQLException {

		Statement st = connection.createStatement();
		st.execute("SHUTDOWN");
		connection.close(); // if there are no other open connection
	}

	/**
	 * @param args
	 *            args
	 * @throws SQLException
	 *             SQLException
	 * @throws MalformedURLException
	 *             MalformedURLException
	 * @throws ClassNotFoundException
	 *             ClassNotFoundException
	 */
	public static void main(String[] args) throws SQLException, MalformedURLException, ClassNotFoundException {
		MaryCache c = new MaryCache(new File("/Users/marc/Desktop/testdb/testDB"), false);
		// c.insertText("TEXT", "RAWMARYXML", "de", "de1", "Welcome to the world of speech synthesis", "");

		String text = c.lookupText("TEXT", "RAWMARYXML", "de", "de1", "Welcome to the world of speech synthesis");
		System.out.println("looked up text: " + text);

		byte[] zeros = new byte[200000];
		// c.insertAudio("TEXT", "de", "de1", "some dummy text", zeros);
		byte[] newones = c.lookupAudio("TEXT", "de", "de1", "some dummy text");
		System.out.println("Retrieved binary data of length " + newones.length);

		c.shutdown();
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy