/*! * 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-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.platform.web.http.api.resources; 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.codehaus.enunciate.Facet; import org.codehaus.enunciate.jaxrs.ResponseCode; import org.codehaus.enunciate.jaxrs.StatusCodes; import org.pentaho.platform.api.engine.IContentGenerator; import org.pentaho.platform.api.engine.IContentInfo; import org.pentaho.platform.api.engine.IPluginManager; import org.pentaho.platform.api.engine.IPluginOperation; import org.pentaho.platform.api.engine.ObjectFactoryException; import org.pentaho.platform.api.engine.PluginBeanException; import org.pentaho.platform.api.repository2.unified.IUnifiedRepository; import org.pentaho.platform.api.repository2.unified.RepositoryFile; import org.pentaho.platform.api.repository2.unified.data.simple.SimpleRepositoryFileData; import org.pentaho.platform.engine.core.system.PentahoSessionHolder; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.platform.repository.RepositoryDownloadWhitelist; import org.pentaho.platform.repository.RepositoryFilenameUtils; import org.pentaho.platform.repository2.unified.webservices.ExecutableFileTypeDto; import org.pentaho.platform.util.RepositoryPathEncoder; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.GenericEntity; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import java.io.FileNotFoundException; import java.io.IOException; import java.io.StringWriter; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import static javax.ws.rs.core.MediaType.*; import static javax.ws.rs.core.Response.Status.NOT_FOUND; /** * The RepositoryResource service retrieves the repository files through various methods.  Allows you to execute repository content. */ @Path ( "/repos" ) public class RepositoryResource extends AbstractJaxRSResource { protected IPluginManager pluginManager = PentahoSystem.get( IPluginManager.class ); private static final Log logger = LogFactory.getLog( RepositoryResource.class ); public static final String GENERATED_CONTENT_PERSPECTIVE = "generatedContent"; //$NON-NLS-1$ protected IUnifiedRepository repository = PentahoSystem.get( IUnifiedRepository.class ); protected RepositoryDownloadWhitelist whitelist; @GET @Path ( "{pathId : .+}/content" ) @Produces ( { WILDCARD } ) @Facet ( name = "Unsupported" ) public Response doGetFileOrDir( @PathParam ( "pathId" ) String pathId ) throws FileNotFoundException { FileResource fileResource = new FileResource( httpServletResponse ); fileResource.setWhitelist( whitelist ); return fileResource.doGetFileOrDir( pathId ); } /** * Takes a pathId to a file and generates a URI that represents the URL to call to generate content from that file. * * <p><b>Example Request:</b><br /> * GET pentaho/api/repos/public:steel%20wheels:Invoice%20(report).prpt/default * </p> * * @param pathId @param pathId * * @return URI that represents a forwarding URL to execute to generate content from the file {pathId}. * * <p><b>Example Response:</b></p> * <pre function="syntax.xml"> * This response does not contain data. * </pre> */ @GET @Path ( "{pathId : .+}/default" ) @Produces ( { WILDCARD } ) @StatusCodes ( { @ResponseCode ( code = 303, condition = "Successfully get the resource." ), @ResponseCode ( code = 404, condition = "Failed to find the resource." ) } ) public Response doExecuteDefault( @PathParam ( "pathId" ) String pathId ) throws FileNotFoundException, MalformedURLException, URISyntaxException { String perspective = null; StringBuffer buffer = null; String url = null; String path = FileResource.idToPath( pathId ); String extension = path.substring( path.lastIndexOf( '.' ) + 1 ); IPluginManager pluginManager = PentahoSystem.get( IPluginManager.class, PentahoSessionHolder.getSession() ); IContentInfo info = pluginManager.getContentTypeInfo( extension ); for ( IPluginOperation operation : info.getOperations() ) { if ( operation.getId().equalsIgnoreCase( "RUN" ) ) { //$NON-NLS-1$ perspective = operation.getPerspective(); break; } } if ( perspective == null ) { perspective = GENERATED_CONTENT_PERSPECTIVE; } buffer = httpServletRequest.getRequestURL(); String queryString = httpServletRequest.getQueryString(); url = buffer.substring( 0, buffer.lastIndexOf( "/" ) + 1 ) + perspective + //$NON-NLS-1$ ( ( queryString != null && queryString.length() > 0 ) ? "?" + httpServletRequest.getQueryString() : "" ); return Response.seeOther( ( new URL( url ) ).toURI() ).build(); } /** * Gets a resource identified by the compound key contextId and resourceId. This request may include additional parameters used to render the resource. * * <p><b>Example Request:</b><br /> * POST pentaho/api/repos/xanalyzer/service/ajax/lookupXmiId * <br /><b>POST data:</b> * <pre function="syntax.xml"> * catalog=t&cube=t&time=1389817320072 * </pre> * </p> * * @param contextId Identifies the context in which the resource should be retrieved. This value may be a repository file ID, repository file extension or plugin ID * @param resourceId Identifies a resource to be retrieved. This value may be a static file residing in a publicly visible plugin folder, repository file ID or content generator ID * @param formParams Any arguments needed to render the resource * * @return A jax-rs Response object with the appropriate status code, header, and body. In many cases this will trigger a streaming operation after it it is returned to the caller.. * * <p><b>Example Response:</b></p> * <pre function="syntax.xml"> * This response does not contain data. * </pre> */ @Path ( "/{contextId}/{resourceId : .+}" ) @POST @Consumes ( APPLICATION_FORM_URLENCODED ) @Produces ( { WILDCARD } ) @StatusCodes ( { @ResponseCode ( code = 200, condition = "Successfully get the resource." ), @ResponseCode ( code = 404, condition = "Failed to find the resource." ) } ) public Response doFormPost( @PathParam ( "contextId" ) String contextId, @PathParam ( "resourceId" ) String resourceId, final MultivaluedMap<String, String> formParams ) throws ObjectFactoryException, PluginBeanException, IOException, URISyntaxException { httpServletRequest = JerseyUtil.correctPostRequest( formParams, httpServletRequest ); if ( logger.isDebugEnabled() ) { for ( Object key : httpServletRequest.getParameterMap().keySet() ) { logger.debug( "param [" + key + "]" ); //$NON-NLS-1$ //$NON-NLS-2$ } } return doService( contextId, resourceId ); } /** * Gets a resource identified by the compound key contextId and resourceId. This request may include additional parameters used to render the resource. * * <p><b>Example Request:</b><br /> * GET pentaho/api/repos/admin-plugin/resources/authenticationProviderModule/authenticationProviderAdmin.html * </p> * * @param contextId Identifies the context in which the resource should be retrieved. This value may be a repository file ID, repository file extension or plugin ID. * @param resourceId Identifies a resource to be retrieved. This value may be a static file residing in a publicly visible plugin folder, repository file ID or content generator ID. * * @return A jax-rs Response object with the appropriate status code, header, and body. * * <p><b>Example Response:</b></p> * <pre function="syntax.xml"> *<!DOCTYPE html> *<html xmlns:pho="http:/www.pentaho.com"> *<head> *<title>Report Parameter UI</title> *<link rel="stylesheet" type="text/css" href="authenticationProviderAdmin.css" /> *<link rel="stylesheet" type="text/css" href="../../../common-ui/resources/web/dojo/dijit/themes/pentaho/pentaho.css" /> *<script type="text/javascript" src="../../../../webcontext.js"></script> *<script type="text/javascript"> *require(["authenticationProviderAdmin"]); *</script> *</head> *<body class="soria" style="border: none"> * *<!-- tree dialog --> *<div id="ldapTreeDialog" data-dojo-type="dijit.Dialog" data-dojo-props='title:"LDAP Browser"' class="dialog"> *<div id="ldapTreeDialogContent" class="dialog-content ldap-tree-padding"> *<div id="ldapTree" data-dojo-props="autoExpand:true"></div> *</div> *<div class="dialog-buttons"> *<div class="container"> *<button id="btn_ldapTreeDialogOk" class="pentaho-button ok-button first"> </button> *<button id="btn_ldapTreeDialogCancel" class="pentaho-button cancel-button last"> </button> *</div> *</div> *</div> * *<!-- override dialog --> *<div id="ldapDirtyDialog" data-dojo-type="dijit.Dialog" class="dialog"> *<div class="dialog-content pentaho-padding-sm"> *<p class="message">You have unsaved changes. Do you want to continue?</p> *</div> *<div class="dialog-buttons"> *<div class="container"> *<button id="btn_ldapDirtyDialogNo" class="pentaho-button no-button first"> </button> *<button id="btn_ldapDirtyDialogYes" class="pentaho-button yes-button last"> </button> *</div> *</div> *</div> * *<!-- test dialog --> *<div id="ldapTestMsgDialog" data-dojo-type="dijit.Dialog" class="dialog"> *<div class="dialog-content pentaho-padding-sm"> *<p class="message"> </p> *</div> *<div class="dialog-buttons"> *<div class="container"> *<button id="btn_hideTest" class="pentaho-button close-button last"> </button> *</div> *</div> *</div> * *<!-- edit server connection --> *<div id="editServerDialog" data-dojo-type="dijit.Dialog" data-dojo-props='title:"Edit External Authentication Server Connection"' class="dialog"> *<div class="dialog-content pentaho-padding-sm"> *<p class="message">Changing server conneciton will remove all current authentication and premissions settings. Do you want to continue?</p> *</div> *<div class="dialog-buttons"> *<div class="container"> *<button id="btn_editServerDialogYesClick" class="pentaho-button ok-button first"> </button> *<button id="btn_editServerDialogNoClick" class="pentaho-button cancel-button last"> </button> *</div> *</div> *</div> * *<!-- edit authentication method --> *<div id="authenticationChangeDialog" data-dojo-type="dijit.Dialog" class="dialog" > *<div class="dialog-content pentaho-padding-sm"> *<p class="message">Changing the authentication method will remove all current authentication and premissions settings. Do you want to continue?</p> *</div> *<div class="dialog-buttons"> *<div class="container"> *<button id="btn_processAuthenticationMethodChange" class="pentaho-button yes-change-button first"> </button> *<button id="btn_authenticationChangeNoClick" class="pentaho-button no-button last"> </button> *</div> *</div> *</div> * * * *<!-- populator dialog --> *<div id="ldapPopTestDialog" data-dojo-type="dijit.Dialog" class="dialog"> *<div class="dialog-content pentaho-padding-sm"> *<div class="groupOption"> *<div class="ldapPopulatorGroupRoleAttributeLabel">Group Role Attribute:</div> *<div class="ldapPopulatorGroupRoleAttributeValue value"></div> *</div> *<div class="groupOption"> *<div class="ldapPopulatorGroupRoleSearchBaseLabel">Group Search Base:</div> *<div class="ldapPopulatorGroupRoleSearchBaseValue value"></div> *</div> *<div class="groupOption"> *<div class="ldapPopulatorGroupSearchFilterLabel">Group Search Filter:</div> *<div class="ldapPopulatorGroupSearchFilterValue value"></div> *</div> *<div class="groupOption"> *<div class="ldapPopulatorRolePrefixLabel">Role Prefix:</div> *<div class="ldapPopulatorRolePrefixValue value"></div> *</div> *<div class="groupOption"> *<div class="ldapUserLabel">User Name:</div> *</div> *<input id="ldapPopTestUserName" type="text" /> *<br /> *<div class="groupOption"> *<label class="ldapUserDomainLabel">User DN:</label> *</div> *<input id="ldapPopTestUserDn" type="text"/> *<br /> *</div> *<div class="dialog-buttons"> *<div class="container"> *<button id="btn_testPopulator" class="pentaho-button ok-button first"> </button> *<button id="btn_hideLdapPropsTest" class="pentaho-button cancel-button last"> </button> *</div> *</div> *</div> * * * *<!-- user test dialog --> *<div id="ldapUserTestDialog" data-dojo-type="dijit.Dialog" class="dialog"> *<div class="dialog-content pentaho-padding-sm"> *<p class="message">With the search base and search filter configuration search for a user name that exists in your LDAP server.</p> *<br/> *<div class="groupOption"> *<div class="ldapUserTestLabel">Search For User:</div> *</div> *<input class="ldapUserTestUserName" type="text" /> *<br /> *</div> *<div class="dialog-buttons"> *<div class="container"> *<button id="btn_testLdapUserSearch" class="pentaho-button ok-button first"> </button> *<button id="btn_hideLdapUserTestDialog" class="pentaho-button cancel-button last"> </button> *</div> *</div> *</div> * * *<div style="padding: 0px;"> *<div class="pentaho-fieldgroup-major titleLabel">Authentication</div> *<br/> *<!-- CONNECTION PARAMS --> *<div id="authenticationSelector"> * *<div class="authenticationMethodLabel authMethod">Authentication Method</div> *<div class="authText authenticationMethodDescriptionLabel"> *Select where user and their log in credentials will be managed: *</div> * *<div class="groupOption"> *<input checked="checked" name="securityProvider" type="radio" value="jackrabbit" /> *<div class="pentahoSecurityLabel authValue">Local - Use basic Pentaho authentication</div> *</div> *<div class="groupOption"> *<input name="securityProvider" type="radio" value="ldap" /> *<div class="ldapSecurityLabel authValue">External - Use LDAP / Active Directory server</div> *</div> *</div> *<br /> * * *<br /> *<div id="ldapConnection" style="display: none"> *<div class="ldapConnectionTitleLabel authMethod">LDAP Server Connection</div> * *<!-- to edit config --> *<div id="ldapConnectionEdit" style="display:block"> *<div class="authText ldapServerUrlLabel">Server URL:</div> *<input class="ldapServerUrlInput authValue adminField" type="text" /> * *<div class="authText ldapUserLabel">User Name:</div> *<input class="ldapUserInput authValue adminField" type="text" /> * *<div class="authText ldapPasswordLabel">Password:</div> *<input class="ldapPasswordInput authValue adminField" type="password" /> * *<br/><br/> *<div class="authText ldapTestConnectionLabel">Test connection to complete LDAP setup</div> *<br/> *<div class="securityConfigButton"> *<button id="testServerConnectionButton" class="pentaho-button testServerConnectionButton" > *</button> *</div> *</div> * *<!-- edited config --> *<div id="ldapConnectionEditor" style="display:none"> *<div class="authText ldapServerUrlLabel" >Server URL:</div> *<div class="groupOption"> *<div class="ldapServerUrlValue authValue"></div> *<div class="pentaho-editbutton" id="btn_editConnection" title="Edit connection"></div> *</div> *</div> * *<br /> * *<div> *<div id="ldapSettingsGroup" style="display: none"> * *<!-- Ldap administration configuration --> *<div id="ldapAdministration"> *<div class="ldapAdministrationTitleLabel authMethod">Pentaho System Administrator</div> *<div class="ldapAdministratiorUserLabel authText">Select user from LDAP server:</div> *<div class="groupOption"> *<input class="ldapAdministratorUserInput adminField" type="text" /> *<button class="adminButton" id="btn_ldapAdministratorUserInput"> </button> *</div> *<div class="ldapAdministrationRoleLabel authText">Select role from LDAP server:</div> *<div class="groupOption"> *<input class="ldapAdministratorRoleInput adminField" type="text" /> *<button class="adminButton" id="btn_ldapAdministratorRoleInput" > </button> *</div> *</div> *<br/><br/> * *<!-- ldap configuration --> *<div class="ldapConfigurationTitle authMethod">LDAP Configuration</div> *<div class="authText" id="customLdapProviderLabel" >Other</div> *<div class="groupOption"> *<select id="ldapTypeSelector"> *<option class="ldapTypeSelectorApacheOption" selected="selected" value="ldapApacheConfiguration">Apache DS</option> *<option class="ldapTypeSelectorCustomOption" value="ldapCustomConfiguration">Custom</option> *</select> *</div> * *<!-- ldap apache configuration --> *<div id="ldapApacheConfiguration" class="ldapApacheConfiguration configuration" style="display: none;"> *<!-- User Base --> *<div class="ldapUserBaseLabel authText">User Base:</div> *<div class="groupOption"> *<input class="ldapUserSearchBaseInput adminField" type="text" /> *<button class="adminButton" id="btn_ldapUserSearchBaseInput"> </button> *</div> *<!-- Group Base --> *<div class="ldapGroupBaseLabel authText">Group Base:</div> *<div class="groupOption"> *<input class="ldapGroupBaseInput adminField" type="text" /> *<button class="adminButton" id="btn_ldapGroupBaseInput" > </button> *</div> * *<div style="display: none"> *<!-- This stuff is hidden but populated for save functions --> *<input class="ldapUserSearchFilterInput" /> * *<input class="ldapRoleBaseInput" /> *<input class="ldapRoleSearchBaseInput" /> *<input class="ldapRoleSearchFilterInput" /> * *<input class="ldapPopulatorGroupRoleAttributeInput" /> *<input class="ldapPopulatorGroupSearchFilterInput" /> *<input class="ldapPopulatorGroupRoleSearchBaseInput" /> *<input class="ldapPopulatorRolePrefixInput" /> *<input class="ldapPopulatorSubtreeInput" name="ldapPopulatorSubtreeInput" type="radio" value="false" /> *<input class="ldapPopulatorUpperCaseInput" name="ldapPopulatorUpperCaseInput" type="radio" value="false" /> *</div> *</div> * *<div id="ldapMicrosoftConfiguration" class="microsoftConfigPanel configuration" style="display: none;"> *<div class="ldapUserBaseLabel authText">User Base:</div> *<div class="groupOption"> *<input class="ldapUserSearchBaseInput adminField" type="text" /> *<button class="adminButton" id="btn_ldapUserSearchBaseInput2" > </button> *</div> * *<div class="ldapGroupBaseLabel authText">Group Base:</div> *<div class="groupOption"> *<input class="ldapGroupBaseInput adminField" type="text" /> *<button class="adminButton" id="btn_ldapGroupBaseInput2"> </button> *</div> * *<div style="display: none"> *<!-- This stuff is hidden but populated for test and save functions --> *<input class="ldapUserSearchFilterInput" /> * *<input class="ldapRoleBaseInput" /> *<input class="ldapRoleSearchBaseInput" /> *<input class="ldapRoleSearchFilterInput" /> * *<input class="ldapPopulatorGroupRoleAttributeInput" /> *<input class="ldapPopulatorGroupSearchFilterInput" /> *<input class="ldapPopulatorGroupRoleSearchBaseInput" /> *<input class="ldapPopulatorRolePrefixInput" /> *<input class="ldapPopulatorSubtreeInput" name="ldapPopulatorSubtreeInput" type="radio" value="false" /> *<input class="ldapPopulatorUpperCaseInput" name="ldapPopulatorUpperCaseInput" type="radio" value="false" /> *</div> *</div> * *<!-- ldap custom configuration --> *<div id="ldapCustomConfiguration" class="ldapCustomConfiguration configuration" style="display: none;"> *<!-- user search configuration --> *<br/> *<span class="ldapCustomUserSearchTitle authMethod">User Search</span> *<br/> *<div> *<div class="ldapUserSearchBaseLabel authText">Search Base:</div> *<input class="ldapUserSearchBaseInput adminField" type="text" /> * *<div class="ldapUserSearchFilderLabel authText">Search Filter:</div> *<input class="ldapUserSearchFilterInput adminField" type="text" /> * *<br/> <br/> * *<div class="securityConfigButton"> *<button class="pentaho-button test-button" id="btn_showLdapUserTestDialog"> </button> *</div> *</div> *<br/> <br/> *<!-- roles configuration --> *<span class="ldapRolesTitle authMethod">Roles</span> *<br/> *<div> *<div class="ldapRoleBaseLabel authText">Role Attribute:</div> *<input class="ldapRoleBaseInput adminField" type="text" /> * *<div class="ldapRoleSearchFilterLabel authText">Role Search Filter:</div> *<input class="ldapRoleSearchFilterInput adminField" type="text" /> * *<div class="ldapRoleSearchBaseLabel authText">Role Search Base:</div> *<input class="ldapRoleSearchBaseInput adminField" type="text" /> * *<br/> <br/> * *<div class="securityConfigButton"> *<button class="pentaho-button test-button" id="btn_testAuthoritiesSearch"> </button> *</div> *</div> *<br/> <br/> *<span class="ldapPopulatorTitle authMethod">Populator</span> *<br/> *<div> *<div class="ldapPopulatorGroupRoleAttributeLabel authText">Group Role Attribute:</div> *<input class="ldapPopulatorGroupRoleAttributeInput adminField" type="text" /> * *<div class="ldapPopulatorGroupRoleSearchBaseLabel authText">Group Search Base:</div> *<input class="ldapPopulatorGroupRoleSearchBaseInput adminField" type="text" /> * *<div class="ldapPopulatorGroupSearchFilterLabel authText">Group Search Filter:</div> *<input class="ldapPopulatorGroupSearchFilterInput adminField" type="text" /> * *<div class="ldapPopulatorRolePrefixLabel authText">Role Prefix:</div> *<input class="ldapPopulatorRolePrefixInput adminField" type="text" /> * *<div class="ldapPopulatorUpperCaseLabel authText">Convert To Upper Case:</div> *<div class="ldapPopulatorUpperCaseDescription groupOption"> *<input name="ldapPopulatorUpperCaseInput" class="ldapPopulatorUpperCaseInput" type="radio" value="true" /> *<label class="yes-button">Yes</label> *<input name="ldapPopulatorUpperCaseInput" class="ldapPopulatorUpperCaseInput" type="radio" checked="checked" value="false" /> *<label class="no-button">No</label> *</div> *<div class="ldapPopulatorSubtreeLabel authText">Subtree:</div> *<div class="ldapPopulatorSubtreeDescription groupOption"> *<input name="ldapPopulatorSubtreeInput" class="ldapPopulatorSubtreeInput" type="radio" value="true" /> *<label class="yes-button">Yes</label> *<input name="ldapPopulatorSubtreeInput" class="ldapPopulatorSubtreeInput" type="radio" checked="checked" value="false" /> *<label class="no-button">No</label> *</div> *<br/> *<div class="securityConfigButton"> *<button class="pentaho-button test-button" id="btn_showPopulatorTestDialog"> </button> *</div> *</div> *</div> *</div> *</div> *</div> *</div> * * *<footer> *<br/><br/> *<div id="buttonDivSave" class="securityConfigButton" style="display: none;"> *<button id="saveConfigButton" class="pentaho-button" >Save</button> *</div> *</footer> *</body> *</html> * </pre> */ @Path ( "/{contextId}/{resourceId : .+}" ) @GET @Produces ( { WILDCARD } ) @StatusCodes ( { @ResponseCode ( code = 200, condition = "Successfully get the resource." ), @ResponseCode ( code = 404, condition = "Failed to find the resource." ) } ) public Response doGet( @PathParam ( "contextId" ) String contextId, @PathParam ( "resourceId" ) String resourceId ) throws ObjectFactoryException, PluginBeanException, IOException, URISyntaxException { if ( logger.isDebugEnabled() ) { for ( Object key : httpServletRequest.getParameterMap().keySet() ) { logger.debug( "param [" + key + "]" ); //$NON-NLS-1$ //$NON-NLS-2$ } } return doService( contextId, resourceId ); } /** * Retrieves the list of supported content type in the platform * * @return list of <code> ExecutableFileTypeDto </code> */ @Path ( "/executableTypes" ) @GET @Produces ( { APPLICATION_XML, APPLICATION_JSON } ) @Facet ( name = "Unsupported" ) public Response getExecutableTypes() { ArrayList<ExecutableFileTypeDto> executableTypes = new ArrayList<ExecutableFileTypeDto>(); for ( String contentType : pluginManager.getContentTypes() ) { IContentInfo contentInfo = pluginManager.getContentTypeInfo( contentType ); ExecutableFileTypeDto executableFileType = new ExecutableFileTypeDto(); executableFileType.setDescription( contentInfo.getDescription() ); executableFileType.setExtension( contentInfo.getExtension() ); executableFileType.setTitle( contentInfo.getTitle() ); executableFileType.setCanSchedule( hasOperationId( contentInfo.getOperations(), "SCHEDULE_NEW" ) ); executableFileType.setCanEdit( hasOperationId( contentInfo.getOperations(), "EDIT" ) ); executableTypes.add( executableFileType ); } final GenericEntity<List<ExecutableFileTypeDto>> entity = new GenericEntity<List<ExecutableFileTypeDto>>( executableTypes ) { }; return Response.ok( entity ).build(); } private boolean hasOperationId( final List<IPluginOperation> operations, final String operationId ) { if ( operations != null && StringUtils.isNotBlank( operationId ) ) { for ( IPluginOperation operation : operations ) { if ( operation != null && StringUtils.isNotBlank( operation.getId() ) ) { if ( operation.getId().equals( operationId ) && StringUtils.isNotBlank( operation.getPerspective() ) ) { return true; } } } } return false; } protected Response doService( String contextId, String resourceId ) throws ObjectFactoryException, PluginBeanException, IOException, URISyntaxException { ctxt( "Is [{0}] a repository file id?", contextId ); //$NON-NLS-1$ if ( contextId.startsWith( ":" ) || contextId.matches( "^[A-z]\t:.*" ) ) { //$NON-NLS-1$ // // The context is a repository file (A) // final RepositoryFile file = repository.getFile( FileResource.idToPath( contextId ) ); if ( file == null ) { logger.error( MessageFormat.format( "Repository file [{0}] not found", contextId ) ); return Response.serverError().build(); } Response response = null; ctxt( "Yep, [{0}] is a repository file id", contextId ); //$NON-NLS-1$ final String ext = RepositoryFilenameUtils.getExtension( file.getName() ); String pluginId = pluginManager.getPluginIdForType( ext ); if ( pluginId == null ) { // A.3.a (faux content generator for .url files) response = getUrlResponse( file, resourceId ); if ( response != null ) { return response; } else { logger.error( MessageFormat.format( "No plugin was found to service content of type [{0}]", ext ) ); return Response.serverError().build(); } } // A.1. response = getPluginFileResponse( pluginId, resourceId ); if ( response != null ) { return response; } // A.2. response = getRepositoryFileResponse( file.getPath(), resourceId ); if ( response != null ) { return response; } // A.3.b (real content generator) CGFactory fac = new RepositoryFileCGFactory( resourceId, file ); response = getContentGeneratorResponse( fac ); if ( response != null ) { return response; } } else { ctxt( "Nope, [{0}] is not a repository file id", contextId ); //$NON-NLS-1$ ctxt( "Is [{0}] is a repository file extension?", contextId ); //$NON-NLS-1$ String pluginId = pluginManager.getPluginIdForType( contextId ); if ( pluginId != null ) { // // The context is a file extension (B) // ctxt( "Yep, [{0}] is a repository file extension", contextId ); //$NON-NLS-1$ // B.1. Response response = getPluginFileResponse( pluginId, resourceId ); if ( response != null ) { return response; } // B.3. CGFactory fac = new ContentTypeCGFactory( resourceId, contextId ); response = getContentGeneratorResponse( fac ); if ( response != null ) { return response; } } else { ctxt( "Nope, [{0}] is not a repository file extension", contextId ); //$NON-NLS-1$ ctxt( "Is [{0}] is a plugin id?", contextId ); //$NON-NLS-1$ if ( pluginManager.getRegisteredPlugins().contains( contextId ) ) { // // The context is a plugin id (C) // ctxt( "Yep, [{0}] is a plugin id", contextId ); //$NON-NLS-1$ pluginId = contextId; // C.1. Response response = getPluginFileResponse( pluginId, resourceId ); if ( response != null ) { return response; } // C.3. CGFactory fac = new DirectCGFactory( resourceId, contextId ); response = getContentGeneratorResponse( fac ); if ( response != null ) { return response; } } else { ctxt( "Nope, [{0}] is not a plugin id", contextId ); //$NON-NLS-1$ logger.warn( MessageFormat.format( "Failed to resolve context [{0}]", contextId ) ); //$NON-NLS-1$ } } } logger.warn( MessageFormat.format( "End of the resolution chain. No resource [{0}] found in context [{1}].", resourceId, contextId ) ); return Response.status( NOT_FOUND ).build(); } abstract class CGFactory implements ContentGeneratorDescriptor { String contentGeneratorId; String command; public CGFactory( String contentGeneratorPath ) { if ( contentGeneratorPath.contains( "/" ) ) { //$NON-NLS-1$ contentGeneratorId = contentGeneratorPath.substring( 0, contentGeneratorPath.indexOf( '/' ) ); command = contentGeneratorPath.substring( contentGeneratorPath.indexOf( '/' ) + 1 ); debug( "decomposing path [{0}] into content generator id [{1}] and command [{2}]", contentGeneratorPath, //$NON-NLS-1$ contentGeneratorId, command ); } else { contentGeneratorId = contentGeneratorPath; } } public String getContentGeneratorId() { return contentGeneratorId; } public String getCommand() { return command; } abstract IContentGenerator create(); abstract GeneratorStreamingOutput getStreamingOutput( IContentGenerator cg ); } class RepositoryFileCGFactory extends ContentTypeCGFactory { RepositoryFile file; public RepositoryFileCGFactory( String contentGeneratorPath, RepositoryFile file ) { super( contentGeneratorPath, file.getName().substring( file.getName().lastIndexOf( '.' ) + 1 ) ); this.file = file; } @Override GeneratorStreamingOutput getStreamingOutput( IContentGenerator cg ) { return new GeneratorStreamingOutput( cg, this, httpServletRequest, httpServletResponse, acceptableMediaTypes, file, command ); } } class ContentTypeCGFactory extends CGFactory { String repoFileExt; public ContentTypeCGFactory( String contentGeneratorPath, String repoFileExt ) { super( contentGeneratorPath ); this.repoFileExt = repoFileExt; } @Override public IContentGenerator create() { return pluginManager.getContentGenerator( repoFileExt, contentGeneratorId ); } @Override GeneratorStreamingOutput getStreamingOutput( IContentGenerator cg ) { return new GeneratorStreamingOutput( cg, this, httpServletRequest, httpServletResponse, acceptableMediaTypes, null, command ); } @Override public String getServicingFileType() { return repoFileExt; } @Override public String getPluginId() { return PentahoSystem.get( IPluginManager.class ).getPluginIdForType( repoFileExt ); } } class DirectCGFactory extends CGFactory { String pluginId; public DirectCGFactory( String contentGeneratorPath, String pluginId ) { super( contentGeneratorPath ); this.pluginId = pluginId; } @Override IContentGenerator create() { return pluginManager.getContentGenerator( null, contentGeneratorId ); } @Override GeneratorStreamingOutput getStreamingOutput( IContentGenerator cg ) { return new GeneratorStreamingOutput( cg, this, httpServletRequest, httpServletResponse, acceptableMediaTypes, null, command ); } @Override public String getServicingFileType() { return null; } @Override public String getPluginId() { return pluginId; } } protected Response getUrlResponse( RepositoryFile file, String resourceId ) throws MalformedURLException, URISyntaxException { String ext = file.getName().substring( file.getName().indexOf( '.' ) + 1 ); if ( !( ext.equals( "url" ) && resourceId.equals( "generatedContent" ) ) ) { return null; //$NON-NLS-1$ //$NON-NLS-2$ } String url = extractUrl( file ); if ( !url.trim().startsWith( "http" ) ) { //$NON-NLS-1$ // if path is relative, prepend FQSURL url = PentahoSystem.getApplicationContext().getFullyQualifiedServerURL() + url; } return Response.seeOther( ( new URL( url ) ).toURI() ).build(); } protected Response getContentGeneratorResponse( CGFactory fac ) { rsc( "Is [{0}] a content generator ID?", fac.getContentGeneratorId() ); //$NON-NLS-1$ final IContentGenerator contentGenerator; try { contentGenerator = fac.create(); } catch ( NoSuchBeanDefinitionException e ) { rsc( "Nope, [{0}] is not a content generator ID.", fac.getContentGeneratorId() ); //$NON-NLS-1$ return null; } if ( contentGenerator == null ) { rsc( "Nope, [{0}] is not a content generator ID.", fac.getContentGeneratorId() ); //$NON-NLS-1$ return null; } rsc( "Yep, [{0}] is a content generator ID. Executing (where command path is {1})..", fac.getContentGeneratorId(), fac.getCommand() ); //$NON-NLS-1$ GeneratorStreamingOutput gso = fac.getStreamingOutput( contentGenerator ); return Response.ok( gso ).build(); } protected Response getPluginFileResponse( String pluginId, String filePath ) throws IOException { rsc( "Is [{0}] a path to a plugin file?", filePath ); //$NON-NLS-1$ if ( pluginManager.isPublic( pluginId, filePath ) ) { PluginResource pluginResource = new PluginResource( httpServletResponse ); Response readFileResponse = pluginResource.readFile( pluginId, filePath ); // TODO: should we assume forbidden means move on in the resolution chain, or abort?? if ( readFileResponse.getStatus() != Status.NOT_FOUND.getStatusCode() ) { rsc( "Yep, [{0}] is a path to a static plugin file", filePath ); //$NON-NLS-1$ return readFileResponse; } } rsc( "Nope, [{0}] is not a path to a static plugin file", filePath ); //$NON-NLS-1$ return null; } protected Response getRepositoryFileResponse( String filePath, String relPath ) throws IOException { rsc( "Is [{0}] a relative path to a repository file, relative to [{1}]?", relPath, filePath ); //$NON-NLS-1$ FileResource fileResource = new FileResource( httpServletResponse ); fileResource.setWhitelist( whitelist ); String path = RepositoryFilenameUtils .separatorsToRepository( RepositoryFilenameUtils.concat( filePath, "../" + relPath ) ); //$NON-NLS-1$ Response response = fileResource.doGetFileOrDir( RepositoryPathEncoder.encodeRepositoryPath( path ).substring( 1 ) ); if ( response.getStatus() != Status.NOT_FOUND.getStatusCode() ) { rsc( "Yep, [{0}] is a repository file", path ); //$NON-NLS-1$ return response; } rsc( "Nope, [{0}] is not a repository file", path ); //$NON-NLS-1$ return null; } private void ctxt( String msg, Object... args ) { debug( "[RESOLVING CONTEXT ID] ==> " + msg, args ); //$NON-NLS-1$ } private void rsc( String msg, Object... args ) { debug( "[RESOLVING RESOURCE ID] ==> " + msg, args ); //$NON-NLS-1$ } private void debug( String msg, Object... args ) { logger.debug( MessageFormat.format( msg, args ) ); } protected String extractUrl( RepositoryFile file ) { SimpleRepositoryFileData data = null; data = repository.getDataForRead( file.getId(), SimpleRepositoryFileData.class ); StringWriter writer = new StringWriter(); try { IOUtils.copy( data.getInputStream(), writer ); } catch ( IOException e ) { return ""; //$NON-NLS-1$ } String props = writer.toString(); StringTokenizer tokenizer = new StringTokenizer( props, "\n" ); //$NON-NLS-1$ while ( tokenizer.hasMoreTokens() ) { String line = tokenizer.nextToken(); int pos = line.indexOf( '=' ); if ( pos > 0 ) { String propname = line.substring( 0, pos ); String value = line.substring( pos + 1 ); if ( ( value != null ) && ( value.length() > 0 ) && ( value.charAt( value.length() - 1 ) == '\r' ) ) { value = value.substring( 0, value.length() - 1 ); } if ( "URL".equalsIgnoreCase( propname ) ) { //$NON-NLS-1$ return value; } } } // No URL found return ""; //$NON-NLS-1$ } public RepositoryDownloadWhitelist getWhitelist() { return whitelist; } public void setWhitelist( RepositoryDownloadWhitelist whitelist ) { this.whitelist = whitelist; } }