/* * Copyright 2002 - 2016 Pentaho Corporation. All rights reserved. * * This software was developed by Pentaho Corporation and is provided under the terms * of the Mozilla Public License, Version 1.1, or any later version. You may not use * this file except in compliance with the license. If you need a copy of the license, * please go to http://www.mozilla.org/MPL/MPL-1.1.txt. The Initial Developer is Pentaho Corporation. * * Software distributed under the Mozilla Public License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. Please refer to * the license for the specific language governing your rights and limitations. */ package org.pentaho.platform.plugin.services.importexport.legacy; import org.apache.commons.io.IOUtils; import org.pentaho.platform.api.engine.IPentahoSession; import org.pentaho.platform.api.engine.IUserRoleListService; import org.pentaho.platform.api.repository.RepositoryException; import org.pentaho.platform.api.repository2.unified.IRepositoryFileData; import org.pentaho.platform.api.repository2.unified.IUnifiedRepository; import org.pentaho.platform.api.repository2.unified.MondrianSchemaAnnotator; import org.pentaho.platform.api.repository2.unified.RepositoryFile; import org.pentaho.platform.api.repository2.unified.RepositoryFilePermission; import org.pentaho.platform.api.repository2.unified.data.node.DataNode; import org.pentaho.platform.api.repository2.unified.data.node.DataProperty; import org.pentaho.platform.api.repository2.unified.data.node.NodeRepositoryFileData; import org.pentaho.platform.engine.core.system.PentahoSessionHolder; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.platform.engine.security.SecurityHelper; import org.pentaho.platform.plugin.action.olap.IOlapServiceException; import org.pentaho.platform.plugin.services.importexport.StreamConverter; import org.pentaho.platform.repository2.ClientRepositoryPaths; import org.pentaho.platform.repository2.unified.fileio.RepositoryFileInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.Callable; import static org.pentaho.platform.repository.solution.filebased.MondrianVfs.ANNOTATIONS_XML; import static org.pentaho.platform.repository.solution.filebased.MondrianVfs.ANNOTATOR_KEY; import static org.pentaho.platform.repository.solution.filebased.MondrianVfs.SCHEMA_XML; public class MondrianCatalogRepositoryHelper { public static final String ETC_MONDRIAN_JCR_FOLDER = ClientRepositoryPaths.getEtcFolderPath() + RepositoryFile.SEPARATOR + "mondrian"; public static final String ETC_OLAP_SERVERS_JCR_FOLDER = ClientRepositoryPaths.getEtcFolderPath() + RepositoryFile.SEPARATOR + "olap-servers"; private boolean isSecured = false; private IUnifiedRepository repository; public MondrianCatalogRepositoryHelper( final IUnifiedRepository repository ) { if ( repository == null ) { throw new IllegalArgumentException(); } this.repository = repository; try { if ( PentahoSystem.get( IUserRoleListService.class ) != null ) { isSecured = true; } } catch ( Throwable t ) { // That's ok. The API throws an exception and there is no method to check // if security is on or off. } initOlapServersFolder(); } @Deprecated public void addSchema( InputStream mondrianFile, String catalogName, String datasourceInfo ) throws Exception { this.addHostedCatalog( mondrianFile, catalogName, datasourceInfo ); } public void addHostedCatalog( InputStream mondrianFile, String catalogName, String datasourceInfo ) throws Exception { RepositoryFile catalog = createCatalog( catalogName, datasourceInfo ); File tempFile = File.createTempFile( "tempFile", null ); tempFile.deleteOnExit(); FileOutputStream outputStream = new FileOutputStream( tempFile ); IOUtils.copy( mondrianFile, outputStream ); RepositoryFile repoFile = new RepositoryFile.Builder( "schema.xml" ).build(); org.pentaho.platform.plugin.services.importexport.RepositoryFileBundle repoFileBundle = new org.pentaho.platform.plugin.services.importexport.RepositoryFileBundle( repoFile, null, ETC_MONDRIAN_JCR_FOLDER + RepositoryFile.SEPARATOR + catalogName + RepositoryFile.SEPARATOR, tempFile, "UTF-8", "text/xml" ); RepositoryFile schema = repository.getFile( ETC_MONDRIAN_JCR_FOLDER + RepositoryFile.SEPARATOR + catalogName + RepositoryFile.SEPARATOR + "schema.xml" ); IRepositoryFileData data = new StreamConverter().convert( repoFileBundle.getInputStream(), repoFileBundle.getCharset(), repoFileBundle.getMimeType() ); if ( schema == null ) { RepositoryFile schemaFile = repository.createFile( catalog.getId(), repoFileBundle.getFile(), data, null ); if ( schemaFile != null ) { // make sure the folder is not set to hidden if the schema is not hidden RepositoryFile catalogFolder = repository.getFile( ETC_MONDRIAN_JCR_FOLDER + RepositoryFile.SEPARATOR + catalogName ); if ( catalogFolder.isHidden() != schemaFile.isHidden() ) { RepositoryFile unhiddenFolder = ( new RepositoryFile.Builder( catalogFolder ) ).hidden( schemaFile.isHidden() ).build(); repository.updateFolder( unhiddenFolder, "" ); } } } else { repository.updateFile( schema, data, null ); } } public void deleteCatalog( String catalogName ) { deleteHostedCatalog( catalogName ); deleteOlap4jServer( catalogName ); } public void deleteHostedCatalog( String catalogName ) { final RepositoryFile catalogNode = repository.getFile( ETC_MONDRIAN_JCR_FOLDER + RepositoryFile.SEPARATOR + catalogName ); if ( catalogNode != null ) { repository.deleteFile( catalogNode.getId(), true, "Deleting hosted catalog: " + catalogName ); } } private void initOlapServersFolder() { final RepositoryFile etcOlapServers = repository.getFile( ETC_OLAP_SERVERS_JCR_FOLDER ); if ( etcOlapServers == null ) { final Callable<Void> callable = new Callable<Void>() { public Void call() throws Exception { repository.createFolder( repository.getFile( RepositoryFile.SEPARATOR + "etc" ).getId(), new RepositoryFile.Builder( "olap-servers" ) .folder( true ) .build(), "Creating olap-servers directory in /etc" ); return null; } }; try { if ( isSecured ) { SecurityHelper.getInstance().runAsSystem( callable ); } else { callable.call(); } } catch ( Exception e ) { throw new RuntimeException( "Failed to create folder /etc/olap-servers in the repository.", e ); } } } public void addOlap4jServer( String name, String className, String URL, String user, String password, Properties props ) { final RepositoryFile etcOlapServers = repository.getFile( ETC_OLAP_SERVERS_JCR_FOLDER ); RepositoryFile entry = repository.getFile( ETC_OLAP_SERVERS_JCR_FOLDER + RepositoryFile.SEPARATOR + name ); if ( entry == null ) { entry = repository.createFolder( etcOlapServers.getId(), new RepositoryFile.Builder( name ) .folder( true ) .build(), "Creating entry for olap server: " + name + " into folder " + ETC_OLAP_SERVERS_JCR_FOLDER ); } final String path = ETC_OLAP_SERVERS_JCR_FOLDER + RepositoryFile.SEPARATOR + name + RepositoryFile.SEPARATOR + "metadata"; // Convert the properties to a serializable XML format. final String xmlProperties; final ByteArrayOutputStream os = new ByteArrayOutputStream(); try { props.storeToXML( os, "Connection properties for server: " + name, "UTF-8" ); xmlProperties = os.toString( "UTF-8" ); } catch ( IOException e ) { // Very bad. Just throw. throw new RuntimeException( e ); } finally { try { os.close(); } catch ( IOException e ) { // Don't care. Just cleaning up. } } final DataNode node = new DataNode( "server" ); node.setProperty( "name", name ); node.setProperty( "className", className ); node.setProperty( "URL", URL ); node.setProperty( "user", user ); node.setProperty( "password", password ); node.setProperty( "properties", xmlProperties ); NodeRepositoryFileData data = new NodeRepositoryFileData( node ); final RepositoryFile metadata = repository.getFile( path ); if ( metadata == null ) { repository.createFile( entry.getId(), new RepositoryFile.Builder( "metadata" ).build(), data, "Creating olap-server metadata for server " + name ); } else { repository.updateFile( metadata, data, "Updating olap-server metadata for server " + name ); } } public void deleteOlap4jServer( String name ) { // Get the /etc/olap-servers/[name] folder. final RepositoryFile serverNode = repository.getFile( ETC_OLAP_SERVERS_JCR_FOLDER + RepositoryFile.SEPARATOR + name ); if ( serverNode != null ) { repository.deleteFile( serverNode.getId(), true, "Deleting olap server: " + name ); } } /** * Provides a list of the catalog names which are not hosted on this server. * (generic olap4j connections) */ public List<String> getOlap4jServers() { final RepositoryFile hostedFolder = repository.getFile( ETC_OLAP_SERVERS_JCR_FOLDER ); if ( hostedFolder == null ) { // This can happen on old systems when this code first kicks in. // The folder gets created in addOlap4jServer return Collections.emptyList(); } final List<String> names = new ArrayList<String>(); for ( RepositoryFile repoFile : repository.getChildren( hostedFolder.getId() ) ) { names.add( repoFile.getName() ); } return names; } public Olap4jServerInfo getOlap4jServerInfo( String name ) { final RepositoryFile serverNode = repository.getFile( ETC_OLAP_SERVERS_JCR_FOLDER + RepositoryFile.SEPARATOR + name + RepositoryFile.SEPARATOR + "metadata" ); if ( serverNode != null ) { return new Olap4jServerInfo( serverNode ); } else { return null; } } /** * Provides a list of the catalog names hosted locally on this server. */ public List<String> getHostedCatalogs() { final List<String> names = new ArrayList<String>(); final RepositoryFile serversFolder = repository.getFile( ETC_MONDRIAN_JCR_FOLDER ); if ( serversFolder != null ) { for ( RepositoryFile repoFile : repository.getChildren( serversFolder.getId() ) ) { names.add( repoFile.getName() ); } } return names; } public HostedCatalogInfo getHostedCatalogInfo( String name ) { final RepositoryFile catalogNode = repository.getFile( ETC_MONDRIAN_JCR_FOLDER + RepositoryFile.SEPARATOR + name + RepositoryFile.SEPARATOR + "metadata" ); if ( catalogNode != null ) { return new HostedCatalogInfo( name, catalogNode ); } else { return null; } } /** * Provides information on a catalog that the server hosts locally. */ public final class HostedCatalogInfo { public final String name; public final String dataSourceInfo; public final String definition; public HostedCatalogInfo( String name, RepositoryFile source ) { final NodeRepositoryFileData data = repository.getDataForRead( source.getId(), NodeRepositoryFileData.class ); this.name = name; this.dataSourceInfo = data.getNode().getProperty( "datasourceInfo" ).getString(); this.definition = data.getNode().getProperty( "definition" ).getString(); } public HostedCatalogInfo( String name, String dataSourceInfo, String definition ) { this.name = name; this.dataSourceInfo = dataSourceInfo; this.definition = definition; } } public final class Olap4jServerInfo { public final String name; public final String className; public final String URL; public final String user; public final String password; public final Properties properties; private Olap4jServerInfo( RepositoryFile source ) { final NodeRepositoryFileData data = repository.getDataForRead( source.getId(), NodeRepositoryFileData.class ); this.name = data.getNode().getProperty( "name" ).getString(); this.className = data.getNode().getProperty( "className" ).getString(); this.URL = data.getNode().getProperty( "URL" ).getString(); final DataProperty userProp = data.getNode().getProperty( "user" ); this.user = userProp == null ? null : userProp.getString(); final DataProperty passwordProp = data.getNode().getProperty( "password" ); this.password = passwordProp == null ? null : passwordProp.getString(); this.properties = new Properties(); final String propertiesXml = data.getNode().getProperty( "properties" ).getString(); try { properties.loadFromXML( new ByteArrayInputStream( propertiesXml.getBytes( "UTF-8" ) ) ); } catch ( Exception e ) { // Very bad. throw new RuntimeException( e ); } } } public Map<String, InputStream> getModrianSchemaFiles( String catalogName ) { Map<String, InputStream> values = new HashMap<String, InputStream>(); RepositoryFile catalogFolder = repository.getFile( ETC_MONDRIAN_JCR_FOLDER + RepositoryFile.SEPARATOR + catalogName ); for ( RepositoryFile repoFile : repository.getChildren( catalogFolder.getId() ) ) { RepositoryFileInputStream is; if ( repoFile.getName().equals( "metadata" ) ) { continue; } try { is = new RepositoryFileInputStream( repoFile, repository ); } catch ( FileNotFoundException e ) { throw new RepositoryException( e ); } values.put( repoFile.getName(), is ); } if ( values.containsKey( ANNOTATIONS_XML ) && values.containsKey( SCHEMA_XML ) ) { return includeAnnotatedSchema( values ); } return values; } private Map<String, InputStream> includeAnnotatedSchema( final Map<String, InputStream> values ) { MondrianSchemaAnnotator annotator = PentahoSystem.get( MondrianSchemaAnnotator.class, ANNOTATOR_KEY, PentahoSessionHolder.getSession() ); try { if ( annotator != null ) { byte[] schemaBytes = IOUtils.toByteArray( values.get( SCHEMA_XML ) ); byte[] annotationBytes = IOUtils.toByteArray( values.get( ANNOTATIONS_XML ) ); values.put( SCHEMA_XML, new ByteArrayInputStream( schemaBytes ) ); values.put( ANNOTATIONS_XML, new ByteArrayInputStream( annotationBytes ) ); values.put( "schema.annotated.xml", annotator.getInputStream( new ByteArrayInputStream( schemaBytes ), new ByteArrayInputStream( annotationBytes ) ) ); } } catch ( IOException e ) { throw new RepositoryException( e ); } return values; } public RepositoryFile getMondrianCatalogFile( String catalogName ) { return repository.getFile( ETC_MONDRIAN_JCR_FOLDER + RepositoryFile.SEPARATOR + catalogName ); } /* * Creates "/etc/mondrian/<catalog>" */ private RepositoryFile createCatalog( String catalogName, String datasourceInfo ) { /* * This is the default implementation. Use Schema name as defined in the mondrian.xml schema. Pending create * alternate implementation. Use catalog name. */ RepositoryFile catalog = getMondrianCatalogFile( catalogName ); if ( catalog == null ) { RepositoryFile etcMondrian = repository.getFile( ETC_MONDRIAN_JCR_FOLDER ); catalog = repository.createFolder( etcMondrian.getId(), new RepositoryFile.Builder( catalogName ).folder( true ) .build(), "" ); } createDatasourceMetadata( catalog, datasourceInfo ); return catalog; } /* * Creates "/etc/mondrian/<catalog>/metadata" and the connection nodes */ private void createDatasourceMetadata( RepositoryFile catalog, String datasourceInfo ) { final String path = ETC_MONDRIAN_JCR_FOLDER + RepositoryFile.SEPARATOR + catalog.getName() + RepositoryFile.SEPARATOR + "metadata"; RepositoryFile metadata = repository.getFile( path ); String definition = "mondrian:/" + catalog.getName(); DataNode node = new DataNode( "catalog" ); node.setProperty( "definition", encodeUrl( definition ) ); node.setProperty( "datasourceInfo", datasourceInfo ); NodeRepositoryFileData data = new NodeRepositoryFileData( node ); if ( metadata == null ) { repository.createFile( catalog.getId(), new RepositoryFile.Builder( "metadata" ).build(), data, null ); } else { repository.updateFile( metadata, data, null ); } } private String makeHostedPath( String name ) { return MondrianCatalogRepositoryHelper.ETC_MONDRIAN_JCR_FOLDER + RepositoryFile.SEPARATOR + name; } private String makeGenericPath( String name ) { return MondrianCatalogRepositoryHelper.ETC_OLAP_SERVERS_JCR_FOLDER + RepositoryFile.SEPARATOR + name; } private boolean isHosted( String name ) { if ( getHostedCatalogs().contains( name ) ) { return true; } else { return false; } } private String makePath( String catalogName ) { if ( isHosted( catalogName ) ) { return makeHostedPath( catalogName ); } else { return makeGenericPath( catalogName ); } } public boolean hasAccess( final String catalogName, final EnumSet<RepositoryFilePermission> perms, IPentahoSession session ) { if ( session == null ) { // No session is equivalent to root access. return true; } // If the connection doesn't exist yet and we're trying to create it, // we need to check the parent folder instead. final String path; if ( !getHostedCatalogs().contains( catalogName ) && !getOlap4jServers().contains( catalogName ) && perms.contains( RepositoryFilePermission.WRITE ) ) { path = isHosted( catalogName ) ? ETC_MONDRIAN_JCR_FOLDER : ETC_OLAP_SERVERS_JCR_FOLDER; } else { path = makePath( catalogName ); } final IPentahoSession origSession = PentahoSessionHolder.getSession(); PentahoSessionHolder.setSession( session ); try { return repository.hasAccess( path, perms ); } catch ( Exception e ) { throw new IOlapServiceException( e ); } finally { PentahoSessionHolder.setSession( origSession ); } } /** * Ensure URLs are properly encoded to accommodate * * @param urlStr * @return */ private String encodeUrl( String urlStr ) { // make sure catalog definition url is properly encoded // try to encode the url before use String protocol = urlStr.substring( 0, urlStr.indexOf( ":" ) + 1 ); String datasourcePath = urlStr.substring( protocol.length() ); String[] folders = datasourcePath.split( "/" ); StringBuilder encodedPath = new StringBuilder( urlStr.length() * 2 ); for ( int i = 0; i < folders.length; i++ ) { String pathPart; try { final Charset urlCharset = Charset.forName( "UTF-8" ); pathPart = URLEncoder.encode( folders[ i ], urlCharset.name() ); } catch ( UnsupportedEncodingException e ) { pathPart = folders[i]; } encodedPath.append( pathPart ); if ( i != folders.length - 1 || urlStr.endsWith( "/" ) ) { encodedPath.append( "/" ); } } return protocol + encodedPath.toString(); } }