/*
* Copyright (c) 2005-2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.
*
*/
package org.wso2.carbon.dataservices.core.admin;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.CellFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.AxisServiceGroup;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.securevault.SecretResolver;
import org.wso2.securevault.SecretResolverFactory;
import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.core.AbstractAdmin;
import org.wso2.carbon.core.RegistryResources;
import org.wso2.carbon.dataservices.common.DBConstants;
import org.wso2.carbon.dataservices.core.DBDeployer;
import org.wso2.carbon.dataservices.core.DBUtils;
import org.wso2.carbon.dataservices.core.DataServiceFault;
import org.wso2.carbon.dataservices.core.description.config.CarbonDataSourceConfig;
import org.wso2.carbon.dataservices.core.description.config.GSpreadConfig;
import org.wso2.carbon.dataservices.core.engine.DataService;
import org.wso2.carbon.dataservices.core.engine.DataServiceSerializer;
import org.wso2.carbon.dataservices.core.engine.PaginatedTableInfo;
import org.wso2.carbon.dataservices.core.script.DSGenerator;
import org.wso2.carbon.dataservices.core.sqlparser.analysers.LexicalAnalyser;
import org.wso2.carbon.dataservices.core.sqlparser.analysers.SyntaxAnalyser;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.jdbc.utils.Transaction;
import org.wso2.carbon.utils.CarbonUtils;
import org.wso2.carbon.utils.DataPaginator;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.*;
/**
* Data Services admin service class, for the basic functions.
*/
public class DataServiceAdmin extends AbstractAdmin {
private static final Log log = LogFactory.getLog(DataServiceAdmin.class);
public DataServiceAdmin() {
}
/**
* Returns data service content as a String.
*
* @param serviceId
* The data service name
* @return The data service configuration data
* @throws AxisFault
*/
public String getDataServiceContentAsString(String serviceId) throws AxisFault {
AxisService axisService = getAxisConfig().getServiceForActivation(serviceId);
StringBuffer fileContents = new StringBuffer();
String filePath;
// construct data service configuration file path
if (axisService != null) {
filePath = ((DataService) axisService.getParameter(DBConstants.DATA_SERVICE_OBJECT)
.getValue()).getDsLocation();
} else {
// Service could be a fault one. Loading contents directly from
// repository
URL repositoryURL = getAxisConfig().getRepository();
filePath = repositoryURL.getPath() + DBDeployer.DEPLOYMENT_FOLDER_NAME + File.separator
+ serviceId + "." + DBConstants.DBS_FILE_EXTENSION;
}
// load file content into a string buffer
if (filePath != null) {
File config = new File(filePath);
try {
FileReader fileReader = new FileReader(config);
BufferedReader in = new BufferedReader(fileReader);
String str;
while ((str = in.readLine()) != null) {
fileContents.append(str + "\n");
}
in.close();
} catch (IOException e) {
throw new AxisFault(
"Error while reading the contents from the service config file for service '"
+ serviceId + "'", e);
}
}
return fileContents.toString();
}
private void setServiceFileHashInRegistry(String serviceGroupName, String hash)
throws Exception {
Registry registry = this.getConfigSystemRegistry();
if (registry == null) {
throw new Exception("WSO2 Registry is not available");
}
boolean transactionStarted = Transaction.isStarted();
try {
if (transactionStarted) {
registry.beginTransaction();
}
String serviceResourcePath = RegistryResources.SERVICE_GROUPS + serviceGroupName;
/* the resource should already exist */
if (registry.resourceExists(serviceResourcePath)) {
Resource serviceResource = registry.get(serviceResourcePath);
serviceResource.setProperty(RegistryResources.ServiceGroupProperties.HASH_VALUE,
hash);
registry.put(serviceResourcePath, serviceResource);
serviceResource.discard();
}
if (transactionStarted) {
registry.commitTransaction();
}
} catch (Throwable e) {
if (transactionStarted) {
registry.rollbackTransaction();
}
log.error("Unable to set the hash value property to service group "
+ serviceGroupName);
throw new Exception(e);
}
}
protected String getDataServiceFileExtension() {
ConfigurationContext configCtx = this.getConfigContext();
String fileExtension = (String) configCtx.getProperty(DBConstants.DB_SERVICE_EXTENSION);
return fileExtension;
}
/**
* Saves the data service in service repository.
* @param serviceName The name of the data service to be saved
* @param serviceHierarchy The hierarchical path of the service
* @param serviceContents The content of the service
* @throws AxisFault
*/
public void saveDataService(String serviceName, String serviceHierarchy,
String serviceContents) throws AxisFault {
String dataServiceFilePath;
ConfigurationContext configCtx = this.getConfigContext();
AxisConfiguration axisConfig = configCtx.getAxisConfiguration();
AxisService axisService = axisConfig.getServiceForActivation(serviceName);
AxisServiceGroup axisServiceGroup;
boolean hotUpdateOrFaulty = false;
if (serviceHierarchy == null) {
serviceHierarchy = "";
}
if (axisService == null) {
/* new service */
String axis2RepoDirectory = axisConfig.getRepository().getPath();
String repoDirectory = (String) configCtx.getProperty(DBConstants.DB_SERVICE_REPO);
String fileExtension = this.getDataServiceFileExtension();
String dataServiceDirectory = axis2RepoDirectory + File.separator + repoDirectory
+ File.separator + serviceHierarchy;
dataServiceFilePath = dataServiceDirectory + File.separator + serviceName + "."
+ fileExtension;
/* create the directory, if it does not exist */
File directory = new File(dataServiceDirectory);
if (!directory.exists() && !directory.mkdirs()) {
throw new AxisFault("Cannot create directory: " + directory.getAbsolutePath());
}
/* check if this is a faulty service */
if (CarbonUtils.getFaultyService(serviceName, configCtx) != null) {
hotUpdateOrFaulty = true;
}
} else {
dataServiceFilePath = ((DataService) axisService.getParameter(
DBConstants.DATA_SERVICE_OBJECT).getValue()).getDsLocation();
axisServiceGroup = axisService.getAxisServiceGroup();
axisServiceGroup.addParameter(CarbonConstants.KEEP_SERVICE_HISTORY_PARAM, "true");
axisServiceGroup.addParameter(CarbonConstants.PRESERVE_SERVICE_HISTORY_PARAM, "true");
axisService.addParameter(CarbonConstants.KEEP_SERVICE_HISTORY_PARAM, "true");
axisService.addParameter(CarbonConstants.PRESERVE_SERVICE_HISTORY_PARAM, "true");
hotUpdateOrFaulty = true;
}
serviceContents = DBUtils.prettifyXML(serviceContents);
/*
* If this is hot-update or a faulty service that is getting deployed
* again, the hash is recalculated and set in the registry, so it will
* think the service did not change, thus the service settings will not
* be cleared. The hash is computed by our own here, also considering
* faulty services, i.e. cannot get an AxisServiceGroup for a faulty
* service.
*/
if (hotUpdateOrFaulty) {
try {
this.setServiceFileHashInRegistry(serviceName,
this.getMD5HashOfString(serviceContents));
} catch (Exception e) {
log.error("Error while saving " + serviceName, e);
throw new AxisFault(
"Error occurred while saving the last access time in the registry for the existing service "
+ serviceName, e);
}
}
/* save contents to .dbs file */
try {
BufferedWriter out = new BufferedWriter(new FileWriter(dataServiceFilePath));
out.write(serviceContents);
out.close();
} catch (IOException e) {
log.error("Error while saving " + serviceName, e);
throw new AxisFault(
"Error occurred while writing the contents for the service config file for the new service "
+ serviceName, e);
}
}
private String getMD5HashOfString(String contents) {
return CarbonUtils.getMD5(contents.getBytes());
}
/**
* Returns list of known database server jdbc urls and driver class names
* from server.xml TODO: Sumedha fix this, so the drivers can be obtained
* from a configuration file, rather TODO: server.xml. As core is a
* component, this shoul be taken from either WSO2 regisrty etc.
*
* @return DBServerData array
*/
@SuppressWarnings("unchecked")
public DBServerData[] getDatabaseUrlDriverList() throws XMLStreamException {
ArrayList<DBServerData> databaseServers = new ArrayList<DBServerData>();
DBServerData databaseServerData;
InputStream stream = getClass().getResourceAsStream(
"/org/wso2/ws/dataservice/admin/database.xml");
StAXOMBuilder builder = new StAXOMBuilder(stream);
OMElement documentElement = builder.getDocumentElement();
Iterator<OMElement> serverElementIterator = documentElement.getChildrenWithName(new QName(
"Server"));
while (serverElementIterator.hasNext()) {
OMElement serverElement = serverElementIterator.next();
OMElement serverIdElement = serverElement.getFirstChildWithName(new QName("Id"));
OMElement jdbcUrlElement = serverElement.getFirstChildWithName(new QName("JdbcURL"));
OMElement driverClassElement = serverElement.getFirstChildWithName(new QName(
"DriverClass"));
databaseServerData = new DBServerData();
databaseServerData.setServerId(serverIdElement.getText());
String text = jdbcUrlElement.getText();
databaseServerData.setJdbcUrl(text);
databaseServerData.setDriverClass(driverClassElement.getText());
// set the protocol
int index = text.indexOf("jdbc:");
if (index > -1) {
String tmp1 = text.substring(index + 5);
index = tmp1.indexOf(":");
String protocol = tmp1.substring(0, index);
databaseServerData.setProtocol(protocol);
} else {
databaseServerData.setProtocol("generic");
}
databaseServers.add(databaseServerData);
}
DBServerData[] databaseServerList = new DBServerData[databaseServers.size()];
databaseServers.toArray(databaseServerList);
return databaseServerList;
}
/**
* This will test a connection to a given database. If connection can be
* made this method will return the status as String, if not, faliour String
* will be return.
*
* @param driverClass
* Driver class
* @param jdbcURL
* JDBC URL
* @param username
* User name
* @param password
* Pass word
* @return String; state
*/
public String testJDBCConnection(String driverClass, String jdbcURL, String username,
String password,String protectedTokens,String passwordProvider) {
String resolvePwd ="";
if (driverClass == null || driverClass.length() == 0) {
String message = "Driver class is missing";
log.debug(message);
return message;
}
if (jdbcURL == null || jdbcURL.length() == 0) {
String message = "Driver connection URL is missing";
log.debug(message);
return message;
}
if (protectedTokens.equals("null")) {
protectedTokens ="";
}
if (passwordProvider.equals("null")) {
passwordProvider ="";
}
if (!(DBUtils.isEmptyString(protectedTokens) && DBUtils.isEmptyString(passwordProvider))) {
OMFactory fac = OMAbstractFactory.getOMFactory();
OMElement dataEl = fac.createOMElement("data", null);
OMElement passwordManagerEl = fac.createOMElement("passwordManager", null);
OMElement protectedTokensEl = fac.createOMElement("protectedTokens", null);
protectedTokensEl.setText(protectedTokens);
OMElement passwordProviderEl = fac.createOMElement("passwordProvider", null);
passwordProviderEl.setText(passwordProvider);
passwordManagerEl.addChild(protectedTokensEl);
passwordManagerEl.addChild(passwordProviderEl);
dataEl.addChild(passwordManagerEl);
SecretResolver secretResolver = SecretResolverFactory.create(dataEl, false);
if (secretResolver != null && secretResolver.isTokenProtected(password)) {
resolvePwd = secretResolver.resolve(password);
} else {
resolvePwd = password;
}
} else {
resolvePwd = password;
}
Connection connection = null;
try {
Class.forName(driverClass.trim());
connection = DriverManager.getConnection(jdbcURL, username, resolvePwd);
String message = "Database connection is successfull with driver class " + driverClass
+ " , jdbc url " + jdbcURL + " and user name " + username;
log.debug(message);
return message;
} catch (SQLException e) {
String message = "Could not connect to database " + jdbcURL + " with username "
+ username;
log.error(message, e);
return message;
} catch (ClassNotFoundException e) {
String message = "Driver class " + driverClass + " can not be loaded";
log.error(message, e);
return message;
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException ignored) {
}
}
}
}
/**
* This will test the connection(retrieve CallFeed) of a Google spreadsheet
* document. If connection can be made this method will return the status as
* String, if not, failure String will be return.
*
* @param user
* - user name
* @param password
* - password
* @param visibility
* - Whether its private or public
* @param documentURL
* - Google spreadsheet URL
* @return string State
*/
public String testGSpreadConnection(String user, String password, String visibility,
String documentURL, String protectedTokens, String passwordProvider) {
String resolvePwd;
if (DBUtils.isEmptyString(documentURL)) {
String message = "Document URL is empty";
log.debug(message);
return message;
}
String key;
SpreadsheetService service = new SpreadsheetService("GSpread Connection Service");
try {
key = GSpreadConfig.extractKey(documentURL);
} catch (DataServiceFault e) {
String message = "Invalid documentURL:" + documentURL;
log.warn(message);
return message;
}
if (protectedTokens == null) {
protectedTokens = "";
}
if (passwordProvider == null) {
passwordProvider = "";
}
if (!(DBUtils.isEmptyString(protectedTokens) && DBUtils.isEmptyString(passwordProvider))) {
OMFactory fac = OMAbstractFactory.getOMFactory();
OMElement dataEl = fac.createOMElement("data", null);
OMElement passwordManagerEl = fac.createOMElement("passwordManager", null);
OMElement protectedTokensEl = fac.createOMElement("protectedTokens", null);
protectedTokensEl.setText(protectedTokens);
OMElement passwordProviderEl = fac.createOMElement("passwordProvider", null);
passwordProviderEl.setText(passwordProvider);
passwordManagerEl.addChild(protectedTokensEl);
passwordManagerEl.addChild(passwordProviderEl);
dataEl.addChild(passwordManagerEl);
SecretResolver secretResolver = SecretResolverFactory.create(dataEl, false);
if (secretResolver.isInitialized() && secretResolver.isTokenProtected(password)) {
resolvePwd = secretResolver.resolve(password);
} else {
resolvePwd = password;
}
} else {
resolvePwd = password;
}
if (!visibility.equals("public")) {
if (DBUtils.isEmptyString(user)) {
String message = "User name field is empty";
log.error(message);
return message;
}
if (DBUtils.isEmptyString(resolvePwd)) {
String message = "Password field is empty";
log.error(message);
return message;
}
try {
service.setUserCredentials(user, resolvePwd);
} catch (AuthenticationException e) {
String message = "Invalid User Credentials";
log.error(message,e);
return message;
}
}
String worksheetFeedURL = GSpreadConfig.BASE_WORKSHEET_URL + key + "/" + visibility
+ "/basic";
try {
URL url = new URL(worksheetFeedURL);
try {
service.getFeed(url, CellFeed.class);
String message = "Google spreadsheet connection is successfull ";
log.debug(message);
return message;
} catch (IOException e) {
String message = "URL Not found:" + documentURL;
log.error(message,e);
return message;
} catch (ServiceException e) {
String message = "URL Not found:" + documentURL;
log.error(message,e);
return message;
}
} catch (MalformedURLException e) {
String message = "Invalid documentURL:" + documentURL;
log.error(message,e);
return message;
}
}
/**
* Return data services
*
* @return names of the data services
* @throws AxisFault
* AxisFault
*/
public String[] getAvailableDS() throws AxisFault {
List<String> serviceList = new ArrayList<String>();
Map<String, AxisService> map = getAxisConfig().getServices();
Set<String> set = map.keySet();
for (String serviceName : set) {
AxisService axisService = getAxisConfig().getService(serviceName);
Parameter parameter = axisService.getParameter(DBConstants.AXIS2_SERVICE_TYPE);
if (parameter != null) {
if (DBConstants.DB_SERVICE_TYPE.equals(parameter.getValue().toString())) {
serviceList.add(serviceName);
}
}
}
return serviceList.toArray(new String[serviceList.size()]);
}
public String[] getCarbonDataSourceNames() {
List<String> list = CarbonDataSourceConfig.getCarbonDataSourceNames();
return list.toArray(new String[list.size()]);
}
public static String[] getColumnNames(String query) throws Exception {
try {
Queue<String> tokenQueue = new LexicalAnalyser(query).getTokens();
if (tokenQueue.size() > 0) {
SyntaxAnalyser sa = new SyntaxAnalyser(tokenQueue);
List<String> columns = sa.processSelectStatement();
return columns.toArray(new String[columns.size()]);
} else {
return new String[0];
}
} catch (Exception e) {
throw new Exception("Error generating the response for the query " + query + ".", e);
}
}
private String[] getdbSchemaList(String datasourceId) throws Exception {
return DSGenerator.getSchemas(datasourceId);
}
public PaginatedTableInfo getPaginatedSchemaInfo(int pageNumber,String datasourceId) throws Exception {
List<String> schemaInfoList = Arrays.asList(getdbSchemaList(datasourceId));
// Pagination
PaginatedTableInfo paginatedTableInfo = new PaginatedTableInfo();
DataPaginator.doPaging(pageNumber, schemaInfoList, paginatedTableInfo);
return paginatedTableInfo;
}
public String[] getTableList(String datasourceId, String dbName, String[] schemas)
throws SQLException
{
return DSGenerator.getTableList(datasourceId, dbName, schemas);
}
public PaginatedTableInfo getPaginatedTableInfo(int pageNumber,String datasourceId, String dbName,
String[] schemas) throws Exception {
List<String> tableInfoList = Arrays.asList(getTableList(datasourceId, dbName, schemas));
// Pagination
PaginatedTableInfo paginatedTableInfo = new PaginatedTableInfo();
DataPaginator.doPaging(pageNumber, tableInfoList, paginatedTableInfo);
return paginatedTableInfo;
}
/**
* Return the generated services name list
*/
public String[] getDSServiceList(String dataSourceId, String dbName, String[] schemas,
String[] tableNames, boolean singleService,String serviceNamespace) throws AxisFault {
DSGenerator generator = new DSGenerator(dataSourceId, dbName, schemas, tableNames, false,serviceNamespace,"");
List<String> serviceNames = new ArrayList<String>();
List<DataService> dsList = generator.getGeneratedServiceList();
for (DataService ds : dsList) {
OMElement element = DataServiceSerializer.serializeDataService(ds);
this.saveDataService(ds.getName(), null, element.toString());
serviceNames.add(ds.getName());
}
return serviceNames.toArray(new String[serviceNames.size()]);
}
/**
* Return the generated service name
*/
public String getDSService(String dataSourceId, String dbName, String[] schemas,
String[] tableNames, boolean singleService,String serviceName,String serviceNamespace) throws AxisFault {
DSGenerator generator = new DSGenerator(dataSourceId, dbName, schemas, tableNames, true,serviceNamespace,serviceName);
DataService dataservice = generator.getGeneratedService();
OMElement element = DataServiceSerializer.serializeDataService(dataservice);
this.saveDataService(dataservice.getName(), null, element.toString());
return generator.getGeneratedService().getName();
}
}