/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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.
*
* Copyright (c) 2002-2016 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.platform.dataaccess.datasource.api;
import com.sun.jersey.core.header.FormDataContentDisposition;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.api.engine.PentahoAccessControlException;
import org.pentaho.platform.api.repository2.unified.IPlatformImportBundle;
import org.pentaho.platform.api.repository2.unified.IUnifiedRepository;
import org.pentaho.platform.api.repository2.unified.RepositoryFileAcl;
import org.pentaho.platform.dataaccess.datasource.wizard.service.ConnectionServiceException;
import org.pentaho.platform.dataaccess.datasource.wizard.service.messages.Messages;
import org.pentaho.platform.engine.core.system.PentahoSessionHolder;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.plugin.action.mondrian.catalog.IAclAwareMondrianCatalogService;
import org.pentaho.platform.plugin.action.mondrian.catalog.IMondrianCatalogService;
import org.pentaho.platform.plugin.action.mondrian.catalog.MondrianCatalog;
import org.pentaho.platform.plugin.services.importer.IPlatformImporter;
import org.pentaho.platform.plugin.services.importer.PlatformImportException;
import org.pentaho.platform.plugin.services.importer.RepositoryFileImportBundle;
import org.pentaho.platform.plugin.services.importexport.legacy.MondrianCatalogRepositoryHelper;
import org.pentaho.platform.repository2.unified.webservices.RepositoryFileAclDto;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
public class AnalysisService extends DatasourceService {
public static final String METADATA_EXT = ".xmi";
private static final String OVERWRITE_IN_REPOS = "overwrite";
private static final String DATASOURCE_NAME = "datasourceName";
private static final String ENABLE_XMLA = "EnableXmla";
private static final String PARAMETERS = "parameters";
private static final String DOMAIN_ID = "domain-id";
private static final String MONDRIAN_MIME_TYPE = "application/vnd.pentaho.mondrian+xml";
private static final String CATALOG_NAME = "catalogName";
private static final String UTF_8 = "UTF-8";
private static final String ZIP_EXTENSION = ".zip";
private static final String MONDRIAN_FILE_EXTENSION = ".mondrian.xml";
private static final String ANNOTATIONS_FILE = "annotations.xml";
private static final Log logger = LogFactory.getLog( AnalysisService.class );
private static final String ANNOTATION_FOLDER = RepositoryFile.SEPARATOR + "etc"
+ RepositoryFile.SEPARATOR + "mondrian" + RepositoryFile.SEPARATOR;
/*
* register the handler in the PentahoSpringObjects.xml for MondrianImportHandler
*/
protected IPlatformImporter importer;
protected IAclAwareMondrianCatalogService aclAwareMondrianCatalogService;
public AnalysisService() {
importer = PentahoSystem.get( IPlatformImporter.class );
if ( mondrianCatalogService instanceof IAclAwareMondrianCatalogService ) {
aclAwareMondrianCatalogService = (IAclAwareMondrianCatalogService) mondrianCatalogService;
}
}
public Map<String, InputStream> doGetAnalysisFilesAsDownload( String analysisId )
throws PentahoAccessControlException {
if ( !canManageACL() ) {
throw new PentahoAccessControlException();
}
MondrianCatalogRepositoryHelper helper = createNewMondrianCatalogRepositoryHelper();
Map<String, InputStream> fileData = helper.getModrianSchemaFiles( analysisId );
super.parseMondrianSchemaName( analysisId, fileData );
return fileData;
}
public void removeAnalysis( String analysisId ) throws PentahoAccessControlException {
try {
ensureDataAccessPermissionCheck();
} catch ( ConnectionServiceException e ) {
throw new PentahoAccessControlException();
}
mondrianCatalogService.removeCatalog( fixEncodedSlashParam( analysisId ), getSession() );
}
public List<String> getAnalysisDatasourceIds() {
List<String> analysisIds = new ArrayList<String>();
List<MondrianCatalog> mockMondrianCatalogList = mondrianCatalogService.listCatalogs( getSession(), false );
Set<String> ids = metadataDomainRepository.getDomainIds();
for ( MondrianCatalog mondrianCatalog : mockMondrianCatalogList ) {
String domainId = mondrianCatalog.getName() + METADATA_EXT;
if ( !ids.contains( domainId ) ) {
analysisIds.add( mondrianCatalog.getName() );
}
}
return analysisIds;
}
public void putMondrianSchema( InputStream dataInputStream, FormDataContentDisposition schemaFileInfo,
String catalogName, // Optional
String origCatalogName, // Optional
String datasourceName, // Optional
boolean overwrite, boolean xmlaEnabledFlag, String parameters,
RepositoryFileAclDto acl )
throws PentahoAccessControlException,
PlatformImportException, Exception {
accessValidation();
String fileName = schemaFileInfo.getFileName();
// sanity check to prevent common mistake - import of .xmi files.
// See BISERVER-12815
fileNameValidation( fileName );
ZipInputStream zis = null;
ByteArrayOutputStream mondrian = null;
ByteArrayOutputStream annotations = null;
if ( fileName.endsWith( ZIP_EXTENSION ) ) {
zis = new ZipInputStream( dataInputStream );
ZipEntry ze = null;
int len = 0;
while ( ( ze = zis.getNextEntry() ) != null ) {
if ( ze.getName().endsWith( MONDRIAN_FILE_EXTENSION ) ) {
IOUtils.copy( zis, mondrian = new ByteArrayOutputStream() );
} else if ( ze.getName().equals( ANNOTATIONS_FILE ) ) {
IOUtils.copy( zis, annotations = new ByteArrayOutputStream() );
}
zis.closeEntry();
}
if ( mondrian != null ) {
dataInputStream = new ByteArrayInputStream( mondrian.toByteArray() );
}
}
try {
processMondrianImport(
dataInputStream, catalogName, origCatalogName, overwrite, xmlaEnabledFlag, parameters, fileName, acl );
if ( annotations != null ) {
String catName = ( catalogName != null ) ? catalogName : fileName.substring( 0, fileName.indexOf( '.' ) );
ByteArrayInputStream annots = new ByteArrayInputStream( annotations.toByteArray() );
IPlatformImportBundle mondrianBundle = new RepositoryFileImportBundle.Builder()
.input( annots ).path( ANNOTATION_FOLDER + catName )
.name( ANNOTATIONS_FILE ).charSet( "UTF-8" ).overwriteFile( true )
.mime( "text/xml" ) .withParam( "domain-id", catName )
.build();
// do import
importer.importFile( mondrianBundle );
logger.debug( "imported mondrian annotations" );
annots.close();
}
} finally {
if ( zis != null ) {
zis.close();
}
if ( mondrian != null ) {
mondrian.close();
}
if ( annotations != null ) {
annotations.close();
}
}
}
public RepositoryFileAclDto getAnalysisDatasourceAcl( String analysisId )
throws PentahoAccessControlException, FileNotFoundException {
checkAnalysisExists( analysisId );
if ( aclAwareMondrianCatalogService != null ) {
final RepositoryFileAcl acl = aclAwareMondrianCatalogService.getAclFor( analysisId );
return acl == null ? null : repositoryFileAclAdapter.marshal( acl );
}
return null;
}
public void setAnalysisDatasourceAcl( String analysisId, RepositoryFileAclDto aclDto )
throws PentahoAccessControlException, FileNotFoundException {
checkAnalysisExists( analysisId );
final RepositoryFileAcl acl = aclDto == null ? null : repositoryFileAclAdapter.unmarshal( aclDto );
if ( aclAwareMondrianCatalogService != null ) {
aclAwareMondrianCatalogService.setAclFor( analysisId, acl );
}
flushDataSources();
}
private void checkAnalysisExists( String analysisId ) throws FileNotFoundException, PentahoAccessControlException {
if ( !canManageACL() ) {
throw new PentahoAccessControlException();
}
if ( mondrianCatalogService.getCatalog( analysisId, PentahoSessionHolder.getSession() ) == null ) {
throw new FileNotFoundException( analysisId + " doesn't exist" );
}
}
/**
* This is the main method that handles the actual Import Handler to persist to PUR
*
* @param dataInputStream
* @param catalogName
* @param overwrite
* @param xmlaEnabledFlag
* @param parameters
* @param fileName
* @param acl acl information for the data source. This parameter is optional.
* @throws PlatformImportException
*/
protected void processMondrianImport( InputStream dataInputStream, String catalogName, String origCatalogName,
boolean overwrite, boolean xmlaEnabledFlag, String parameters, String fileName,
RepositoryFileAclDto acl )
throws PlatformImportException {
boolean overWriteInRepository = determineOverwriteFlag( parameters, overwrite );
IPlatformImportBundle bundle =
createPlatformBundle(
parameters, dataInputStream, catalogName, overWriteInRepository, fileName, xmlaEnabledFlag, acl );
if ( isChangeCatalogName( origCatalogName, bundle ) ) {
IMondrianCatalogService catalogService =
PentahoSystem.get( IMondrianCatalogService.class, PentahoSessionHolder.getSession() );
catalogService.removeCatalog( origCatalogName, PentahoSessionHolder.getSession() );
}
if ( isOverwriteAnnotations( parameters, overWriteInRepository ) ) {
IMondrianCatalogService catalogService =
PentahoSystem.get( IMondrianCatalogService.class, PentahoSessionHolder.getSession() );
List<MondrianCatalog> catalogs = catalogService.listCatalogs( PentahoSessionHolder.getSession(), false );
for ( MondrianCatalog catalog : catalogs ) {
if ( catalog.getName().equals( bundle.getName() ) ) {
catalogService.removeCatalog( bundle.getName(), PentahoSessionHolder.getSession() );
break;
}
}
}
importer.importFile( bundle );
}
private boolean isChangeCatalogName( final String origCatalogName, final IPlatformImportBundle bundle ) {
// MONDRIAN-1731
// we are importing a mondrian catalog with a new schema (during edit), remove the old catalog first
// processing the bundle without doing this will result in a new catalog, giving the effect of adding
// a catalog rather than editing
return !StringUtils.isEmpty( origCatalogName ) && !bundle.getName().equals( origCatalogName );
}
private boolean isOverwriteAnnotations( final String parameters, final boolean overWriteInRepository ) {
return overWriteInRepository && !"true".equalsIgnoreCase( getValue( parameters, "retainInlineAnnotations" ) );
}
/**
* helper method to calculate the overwrite in repos flag from parameters or passed value
*
* @param parameters
* @param overWriteInRepository
* @return boolean if overwrite is allowed
*/
private boolean determineOverwriteFlag( String parameters, boolean overWriteInRepository ) {
String overwriteStr = getValue( parameters, OVERWRITE_IN_REPOS );
if ( overwriteStr != null ) {
overWriteInRepository = "True".equalsIgnoreCase( overwriteStr ) ? true : false;
} // if there is a conflict - parameters win?
return overWriteInRepository;
}
/**
* helper method to create the platform bundle used by the Jcr repository
*
* @param parameters
* @param dataInputStream
* @param catalogName
* @param overWriteInRepository
* @param fileName
* @param xmlaEnabled
* @param acl acl information for the data source. This parameter is optional.
* @return IPlatformImportBundle
*/
private IPlatformImportBundle createPlatformBundle( String parameters, InputStream dataInputStream,
String catalogName, boolean overWriteInRepository,
String fileName, boolean xmlaEnabled, RepositoryFileAclDto acl ) {
byte[] bytes = null;
try {
bytes = IOUtils.toByteArray( dataInputStream );
if ( bytes.length == 0 && catalogName != null ) {
MondrianCatalogRepositoryHelper helper =
new MondrianCatalogRepositoryHelper( PentahoSystem.get( IUnifiedRepository.class ) );
Map<String, InputStream> fileData = helper.getModrianSchemaFiles( catalogName );
dataInputStream = fileData.get( "schema.xml" );
bytes = IOUtils.toByteArray( dataInputStream );
}
} catch ( IOException e ) {
logger.error( e );
}
String datasource = getValue( parameters, "Datasource" );
String domainId =
this.determineDomainCatalogName( parameters, catalogName, fileName, new ByteArrayInputStream( bytes ) );
String sep = ";";
if ( StringUtils.isEmpty( parameters ) ) {
parameters = "Provider=mondrian";
parameters += sep + DATASOURCE_NAME + "=" + datasource;
parameters += sep + ENABLE_XMLA + "=" + xmlaEnabled;
}
RepositoryFileImportBundle.Builder bundleBuilder =
new RepositoryFileImportBundle.Builder().input( new ByteArrayInputStream( bytes ) ).charSet( UTF_8 ).hidden(
false ).name( domainId ).overwriteFile( overWriteInRepository ).mime( MONDRIAN_MIME_TYPE ).withParam(
PARAMETERS, parameters ).withParam( DOMAIN_ID, domainId );
if ( acl != null ) {
bundleBuilder.acl( repositoryFileAclAdapter.unmarshal( acl ) ).applyAclSettings( true );
}
bundleBuilder.withParam( ENABLE_XMLA, Boolean.toString( xmlaEnabled ) );
IPlatformImportBundle bundle = bundleBuilder.build();
return bundle;
}
/**
* convert string to property to do a lookup "Provider=Mondrian;DataSource=Pentaho"
*
* @param parameters
* @param key
* @return
*/
private String getValue( String parameters, String key ) {
mondrian.olap.Util.PropertyList propertyList = mondrian.olap.Util.parseConnectString( parameters );
return propertyList.get( key );
}
String getSchemaName( String encoding, InputStream inputStream ) throws XMLStreamException, IOException {
String domainId = null;
XMLStreamReader reader = null;
try {
XMLInputFactory factory = XMLInputFactory.newInstance();
factory.setProperty( XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false );
factory.setProperty( XMLInputFactory.IS_COALESCING, Boolean.TRUE );
if ( StringUtils.isEmpty( encoding ) ) {
reader = factory.createXMLStreamReader( inputStream );
} else {
reader = factory.createXMLStreamReader( inputStream, encoding );
}
while ( reader.next() != XMLStreamReader.END_DOCUMENT ) {
if ( reader.getEventType() == XMLStreamReader.START_ELEMENT
&& reader.getLocalName().equalsIgnoreCase( "Schema" ) ) {
domainId = reader.getAttributeValue( "", "name" );
return domainId;
}
}
} finally {
if ( reader != null ) {
reader.close();
}
inputStream.reset();
}
return domainId;
}
/**
* helper method to calculate the domain id from the parameters, file name, or pass catalog
*
* @param parameters
* @param catalogName
* @param fileName
* @return Look up name from parameters or file name or passed in catalog name
*/
private String determineDomainCatalogName( String parameters, String catalogName, String fileName,
InputStream inputStream ) {
/*
* Try to resolve the domainId out of the mondrian schema name. If not present then use the catalog name parameter
* or finally the file name.
*/
String domainId = null;
try {
domainId = getSchemaName( null, inputStream );
} catch ( Exception e ) {
try {
domainId = getSchemaName( UTF_8, inputStream );
} catch ( Exception e1 ) {
logger.error( e1 );
}
}
if ( !StringUtils.isEmpty( domainId ) ) {
return domainId;
}
domainId = ( getValue( parameters, CATALOG_NAME ) == null ) ? catalogName : getValue( parameters, CATALOG_NAME );
if ( domainId == null || "".equals( domainId ) ) {
if ( fileName.contains( "." ) ) {
domainId = fileName.substring( 0, fileName.indexOf( "." ) );
} else {
domainId = fileName;
}
} else {
if ( domainId.contains( "." ) ) {
domainId = domainId.substring( 0, domainId.indexOf( "." ) );
}
}
return domainId;
}
protected MondrianCatalogRepositoryHelper createNewMondrianCatalogRepositoryHelper() {
return new MondrianCatalogRepositoryHelper( PentahoSystem.get( IUnifiedRepository.class ) );
}
protected boolean canAdministerCheck() {
return super.canAdminister();
}
protected void ensureDataAccessPermissionCheck() throws ConnectionServiceException {
super.ensureDataAccessPermission();
}
protected void accessValidation() throws PentahoAccessControlException {
super.validateAccess();
}
protected IPentahoSession getSession() {
return PentahoSessionHolder.getSession();
}
private void fileNameValidation( final String fileName ) throws PlatformImportException {
if ( fileName == null ) {
throw new PlatformImportException( Messages.getString( "AnalysisService.ERROR_001_ANALYSIS_DATASOURCE_ERROR" ),
PlatformImportException.PUBLISH_GENERAL_ERROR );
}
if ( fileName.endsWith( METADATA_EXT ) ) {
throw new PlatformImportException( Messages.getString( "AnalysisService.ERROR_002_ANALYSIS_DATASOURCE_ERROR" ),
PlatformImportException.PUBLISH_GENERAL_ERROR );
}
}
}