/* * 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) 2016 Pentaho Corporation. All rights reserved. */ package org.pentaho.marketplace.domain.model.entities.serialization; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.marketplace.domain.model.entities.DevelopmentStage; import org.pentaho.marketplace.domain.model.entities.MarketEntryType; import org.pentaho.marketplace.domain.model.entities.interfaces.ICategory; import org.pentaho.marketplace.domain.model.entities.interfaces.IDevelopmentStage; import org.pentaho.marketplace.domain.model.entities.interfaces.IPlugin; import org.pentaho.marketplace.domain.model.entities.interfaces.IPluginVersion; import org.pentaho.marketplace.domain.model.factories.interfaces.ICategoryFactory; import org.pentaho.marketplace.domain.model.factories.interfaces.IPluginFactory; import org.pentaho.marketplace.domain.model.factories.interfaces.IPluginVersionFactory; import org.pentaho.marketplace.util.XmlParserFactoryProducer; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.InputStream; import java.io.StringReader; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; public class MarketplaceXmlSerializer implements IMarketplaceXmlSerializer { // region constants private static final String[] EMPTY_STRING_ARRAY = new String[ 0 ]; protected static final String OSGI_URL_SUFFIX = ".kar"; // endregion // region Properties private IPluginFactory pluginFactory; private IPluginVersionFactory pluginVersionFactory; private ICategoryFactory categoryFactory; private Log logger = LogFactory.getLog( this.getClass() ); private Log getLogger() { return this.logger; } private DocumentBuilderFactory getDocumentBuilderFactory() { return this.documentBuilderFactory; } private DocumentBuilderFactory documentBuilderFactory; private XPath xpath; // endregion public MarketplaceXmlSerializer( IPluginFactory pluginFactory, IPluginVersionFactory pluginVersionFactory, ICategoryFactory categoryFactory ) { this.pluginFactory = pluginFactory; this.pluginVersionFactory = pluginVersionFactory; this.categoryFactory = categoryFactory; this.xpath = XPathFactory.newInstance().newXPath(); this.documentBuilderFactory = createDocumentBuilderFactory(); } private DocumentBuilderFactory createDocumentBuilderFactory() { DocumentBuilderFactory secureDocBuilderFactory = null; try { secureDocBuilderFactory = XmlParserFactoryProducer.createSecureDocBuilderFactory(); } catch ( ParserConfigurationException e ) { this.getLogger().error( e ); } return secureDocBuilderFactory; } @Override public Map<String, IPlugin> getPlugins( InputStream xmlStream ) { Map<String, IPlugin> plugins = Collections.emptyMap(); try { DocumentBuilder db = this.getDocumentBuilderFactory().newDocumentBuilder(); Document document = db.parse( xmlStream ); plugins = this.getPlugins( document ); } catch ( Exception e ) { this.getLogger().error( "Error getting plugins from marketplace xml.", e ); // TODO: throw app exception in order to return error in endpoint } return plugins; } @Override public Map<String, IPlugin> getPlugins( String xml ) { Map<String, IPlugin> plugins = Collections.emptyMap(); try { DocumentBuilder db = this.getDocumentBuilderFactory().newDocumentBuilder(); Document document = db.parse( new InputSource( new StringReader( xml ) ) ); plugins = this.getPlugins( document ); } catch ( Exception e ) { this.getLogger().error( "Error getting plugins from marketplace xml: " + xml, e ); // TODO: throw app exception in order to return error in endpoint } return plugins; } @Override public Map<String, IPlugin> getPlugins( Document marketplaceMetadataDocument ) throws XPathExpressionException { NodeList plugins = marketplaceMetadataDocument.getElementsByTagName( "market_entry" ); Map<String, IPlugin> pluginMap = new HashMap<>(); for ( int i = 0; i < plugins.getLength(); i++ ) { Element pluginElement = (Element) plugins.item( i ); IPlugin plugin = getPlugin( pluginElement ); pluginMap.put( plugin.getId(), plugin ); } return pluginMap; } private IPlugin getPlugin( Element pluginElement ) throws XPathExpressionException { IPlugin plugin = this.pluginFactory.create(); plugin.setId( getElementChildValue( pluginElement, "id" ) ); plugin.setName( getElementChildValue( pluginElement, "name" ) ); plugin.setDescription( getElementChildValue( pluginElement, "description" ) ); plugin.setAuthorName( getElementChildValue( pluginElement, "author" ) ); plugin.setAuthorUrl( getElementChildValue( pluginElement, "author_url" ) ); plugin.setAuthorLogo( getElementChildValue( pluginElement, "author_logo" ) ); plugin.setImg( getElementChildValue( pluginElement, "img" ) ); plugin.setSmallImg( getElementChildValue( pluginElement, "small_img" ) ); plugin.setDocumentationUrl( getElementChildValue( pluginElement, "documentation_url" ) ); plugin.setInstallationNotes( getElementChildValue( pluginElement, "installation_notes" ) ); plugin.setLicense( getElementChildValue( pluginElement, "license" ) ); plugin.setLicenseName( getElementChildValue( pluginElement, "license_name" ) ); plugin.setLicenseText( getElementChildValue( pluginElement, "license_text" ) ); plugin.setDependencies( getElementChildValue( pluginElement, "dependencies" ) ); plugin.setType( MarketEntryType.valueOf( getElementChildValue( pluginElement, "type" ) ) ); plugin.setCategory( this.getCategory( pluginElement ) ); NodeList availableVersionsNode = (NodeList) this.xpath.evaluate( "versions/version", pluginElement, XPathConstants.NODESET ); Collection<IPluginVersion> versions = this.getPluginVersions( availableVersionsNode ); plugin.setVersions( versions ); NodeList availableScreenshotsNode = (NodeList) xpath.evaluate( "screenshots/screenshot", pluginElement, XPathConstants.NODESET ); String[] screenshots = getScreenshots( availableScreenshotsNode ); plugin.setScreenshots( screenshots ); return plugin; } private IPluginVersion getPluginVersion( Element versionElement ) throws XPathExpressionException { IPluginVersion version = this.pluginVersionFactory.create(); version.setBranch( getElementChildValue( versionElement, "branch" ) ); version.setName( getElementChildValue( versionElement, "name" ) ); version.setVersion( getElementChildValue( versionElement, "version" ) ); String downloadUrl = getElementChildValue( versionElement, "package_url" ); if ( downloadUrl != null ) { downloadUrl = downloadUrl.trim(); version.setDownloadUrl( downloadUrl ); version.setIsOsgi( downloadUrl.endsWith( OSGI_URL_SUFFIX ) ); } version.setSamplesDownloadUrl( getElementChildValue( versionElement, "samples_url" ) ); version.setDescription( getElementChildValue( versionElement, "description" ) ); version.setChangelog( getElementChildValue( versionElement, "changelog" ) ); version.setBuildId( getElementChildValue( versionElement, "build_id" ) ); version.setReleaseDate( getElementChildValue( versionElement, "releaseDate" ) ); version.setMinParentVersion( getElementChildValue( versionElement, "min_parent_version" ) ); version.setMaxParentVersion( getElementChildValue( versionElement, "max_parent_version" ) ); version.setDevelopmentStage( getDevelopmentStage( versionElement ) ); return version; } private Collection<IPluginVersion> getPluginVersions( NodeList versionsElement ) throws XPathExpressionException { if ( versionsElement.getLength() == 0 ) { return Collections.emptyList(); } Collection<IPluginVersion> versions = new ArrayList<>(); for ( int j = 0; j < versionsElement.getLength(); j++ ) { Element versionElement = (Element) versionsElement.item( j ); IPluginVersion pv = this.getPluginVersion( versionElement ); versions.add( pv ); } return versions; } private String[] getScreenshots( NodeList screenshotsElement ) { if ( screenshotsElement.getLength() == 0 ) { return EMPTY_STRING_ARRAY; } String[] screenshots = new String[ screenshotsElement.getLength() ]; for ( int j = 0; j < screenshotsElement.getLength(); j++ ) { Element screenshotElement = (Element) screenshotsElement.item( j ); screenshots[ j ] = screenshotElement.getTextContent(); } return screenshots; } private String getElementChildValue( Element element, String child ) throws XPathExpressionException { Element childElement = (Element) xpath.evaluate( child, element, XPathConstants.NODE ); if ( childElement != null ) { return childElement.getTextContent(); } else { return null; } } private ICategory getCategory( Element pluginElement ) throws XPathExpressionException { final String CATEGORY_ELEMENT_NAME = "category"; Element categoryElement = (Element) xpath.evaluate( CATEGORY_ELEMENT_NAME, pluginElement, XPathConstants.NODE ); if ( categoryElement == null ) { return null; } return this.getCategoryFromCategoryElement( categoryElement ); } private ICategory getCategoryFromCategoryElement( Element categoryElement ) throws XPathExpressionException { final String PARENT_ELEMENT_NAME = "parent"; final String NAME_ELEMENT_NAME = "name"; ICategory parent = null; Element parentElement = (Element) xpath.evaluate( PARENT_ELEMENT_NAME, categoryElement, XPathConstants.NODE ); if ( parentElement != null ) { parent = getCategoryFromCategoryElement( parentElement ); } String name = getElementChildValue( categoryElement, NAME_ELEMENT_NAME ); ICategory category = this.categoryFactory.create( name, parent ); return category; } /** * Parses the version element to get the development stage * * @param versionElement where the development stage element is contained * @return the parsed development stage */ private IDevelopmentStage getDevelopmentStage( Element versionElement ) throws XPathExpressionException { final String DEVELOPMENT_STAGE_ELEMENT_NAME = "development_stage"; final String DEVELOPMENT_STAGE_LANE_ELEMENT_NAME = "lane"; final String DEVELOPMENT_STAGE_PHASE_ELEMENT_NAME = "phase"; Element devStageElement = (Element) xpath.evaluate( DEVELOPMENT_STAGE_ELEMENT_NAME, versionElement, XPathConstants.NODE ); if ( devStageElement == null ) { return null; } String lane = this.getElementChildValue( devStageElement, DEVELOPMENT_STAGE_LANE_ELEMENT_NAME ); String phase = this.getElementChildValue( devStageElement, DEVELOPMENT_STAGE_PHASE_ELEMENT_NAME ); // TODO: switch to factory to allow DI? return new DevelopmentStage( lane, phase ); } @Override public IPluginVersion getInstalledVersion( String xml ) { return this.getInstalledVersion( new InputSource( new StringReader( xml ) ) ); } @Override public IPluginVersion getInstalledVersion( InputSource inputDocument ) { IPluginVersion version = null; try { DocumentBuilder db = this.getDocumentBuilderFactory().newDocumentBuilder(); Document document = db.parse( inputDocument ); version = this.getInstalledVersion( document ); } catch ( Exception e ) { this.getLogger().error( "Error getting plugin version from version xml.", e ); // TODO: throw app exception in order to return error in endpoint } return version; } @Override public IPluginVersion getInstalledVersion( Document installedVersionDocument ) { NodeList versionElements = installedVersionDocument.getElementsByTagName( "version" ); if ( versionElements.getLength() == 0 ) { return null; } Element versionElement = (Element) versionElements.item( 0 ); IPluginVersion version = this.pluginVersionFactory.create(); version.setBranch( versionElement.getAttribute( "branch" ) ); version.setVersion( versionElement.getTextContent() ); version.setBuildId( versionElement.getAttribute( "buildId" ) ); return version; } }