Java tutorial
/** * Copyright (c) 2015-2018 Linagora * * This program/library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the License, or (at your * option) any later version. * * This program/library 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. * * You should have received a copy of the GNU Lesser General Public License * along with this program/library; If not, see http://www.gnu.org/licenses/ * for the GNU Lesser General Public License version 2.1. */ package org.ow2.petals.roboconf.installer; import static org.ow2.petals.roboconf.Constants.COMPONENT_VARIABLE_NAME_PROPERTIESFILE; import static org.ow2.petals.roboconf.Constants.ROBOCONF_COMPONENT_ABTRACT_CONTAINER; import static org.ow2.petals.roboconf.Constants.ROBOCONF_COMPONENT_ABTRACT_JBI_COMPONENT; import static org.ow2.petals.roboconf.Constants.ROBOCONF_COMPONENT_BC_COMPONENT; import static org.ow2.petals.roboconf.Constants.ROBOCONF_COMPONENT_SE_COMPONENT; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigDecimal; import java.net.MalformedURLException; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.logging.Level; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.io.IOUtils; import org.ow2.petals.admin.api.exception.ArtifactAdministrationException; import org.ow2.petals.admin.api.exception.ContainerAdministrationException; import org.ow2.petals.admin.api.exception.DuplicatedServiceException; import org.ow2.petals.admin.api.exception.MissingServiceException; import org.ow2.petals.jbi.descriptor.original.JBIDescriptorBuilder; import org.ow2.petals.jbi.descriptor.original.generated.Identification; import org.ow2.petals.jbi.descriptor.original.generated.Jbi; import org.ow2.petals.jbi.descriptor.original.generated.ServiceAssembly; import org.ow2.petals.jbi.descriptor.original.generated.ServiceUnit; import org.ow2.petals.jbi.descriptor.original.generated.Target; import org.ow2.petals.roboconf.Utils; import com.ebmwebsourcing.easycommons.properties.PropertiesException; import com.ebmwebsourcing.easycommons.properties.PropertiesHelper; import net.roboconf.core.model.beans.Component; import net.roboconf.core.model.beans.Import; import net.roboconf.core.model.beans.Instance; import net.roboconf.core.model.beans.Instance.InstanceStatus; import net.roboconf.core.model.helpers.InstanceHelpers; import net.roboconf.plugin.api.PluginException; import net.roboconf.plugin.api.PluginInterface; public class PluginPetalsSuInstaller extends PluginPetalsAbstractInstaller implements PluginInterface { private static final String PLUGIN_NAME = "petals-su-installer"; private static final String SA_NAME_TEMPLATE = "sa-%s"; public PluginPetalsSuInstaller() throws DuplicatedServiceException, MissingServiceException { super(); } @Override public void deploy(final Instance instance) throws PluginException { // NOP: Hack to workaround https://github.com/roboconf/roboconf-platform/issues/331 // TODO: Rename localDeploy(...) into deploy(...) when https://github.com/roboconf/roboconf-platform/issues/331 // will be fixed } private void localDeploy(final Instance instance) throws PluginException { final File instanceDirectory = InstanceHelpers.findInstanceDirectoryOnAgent(instance); final File suFile = new File(instanceDirectory, instance.getName() + ".zip"); // TODO: Try to improve access to the java SPI of Petals Admin using a dedicated bundle for Petals Admin API // instead of setting the classloader final ClassLoader old = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(PluginPetalsSuInstaller.class.getClassLoader()); try { final File saFile = File.createTempFile(PluginPetalsSuInstaller.getSAName(instance), ".zip"); // Generate the SA including the SU this.logger.fine(this.agentId + ": generating the SA (" + saFile.getAbsolutePath() + ") for instance " + instance + " from the SU " + suFile.getAbsolutePath()); final Jbi saJbiDescriptor = new Jbi(); saJbiDescriptor.setVersion(new BigDecimal(1)); final ServiceAssembly serviceAssembly = new ServiceAssembly(); final Identification saIdentifiaction = new Identification(); saIdentifiaction.setName(PluginPetalsSuInstaller.getSAName(instance)); saIdentifiaction.setDescription(""); serviceAssembly.setIdentification(saIdentifiaction); final ServiceUnit serviceUnit = new ServiceUnit(); final Identification suIdentifiaction = new Identification(); suIdentifiaction.setName(instance.getName()); suIdentifiaction.setDescription(""); serviceUnit.setIdentification(suIdentifiaction); final Target componentTarget = new Target(); componentTarget.setArtifactsZip(suFile.getName()); componentTarget.setComponentName(instance.getParent().getName()); serviceUnit.setTarget(componentTarget); serviceAssembly.getServiceUnit().add(serviceUnit); saJbiDescriptor.setServiceAssembly(serviceAssembly); try (final OutputStream saOutputStream = new FileOutputStream(saFile); final ZipOutputStream zipOutputStream = new ZipOutputStream(saOutputStream)) { // ZIP entry associated to the SU archive this.logger.fine(this.agentId + ": putting the SU into the SA"); final ZipEntry suEntry = new ZipEntry(suFile.getName()); zipOutputStream.putNextEntry(suEntry); try (final InputStream suInputStream = new FileInputStream(suFile)) { IOUtils.copy(suInputStream, zipOutputStream); } zipOutputStream.closeEntry(); // ZIP entry associated to the JBI descriptor this.logger.fine(this.agentId + ": putting the JBI descriptor into the SA"); final ZipEntry jbiDescrEntry = new ZipEntry( JBIDescriptorBuilder.JBI_DESCRIPTOR_RESOURCE_IN_ARCHIVE); zipOutputStream.putNextEntry(jbiDescrEntry); JBIDescriptorBuilder.getInstance().writeXMLJBIdescriptor(saJbiDescriptor, zipOutputStream); zipOutputStream.closeEntry(); } // Deploy the SA previously generated this.logger.fine(this.agentId + ": deploying the SA for instance " + instance); this.connectToContainer(this.retrieveContainerInstance(instance)); try { this.adminApi.newArtifactAdministration().deployAndStartArtifact(saFile.toURI().toURL(), true); } catch (final MalformedURLException e) { // This error should not occur because the URL is generated from a local file this.logger.log(Level.WARNING, "An error occurs", e); } catch (final NumberFormatException e) { throw new PluginException("Invalid value for JMX port (Not a number)", e); } finally { this.adminApi.disconnect(); } } catch (final Exception e) { this.logger.log(Level.SEVERE, "An error occurs", e); throw new PluginException(e); } finally { Thread.currentThread().setContextClassLoader(old); } } @Override public void start(final Instance suInstance) throws PluginException { // NOP: Hack to workaround https://github.com/roboconf/roboconf-platform/issues/331 // TODO: Remove when https://github.com/roboconf/roboconf-platform/issues/331 will be fixed this.localDeploy(suInstance); this.updatePropertiesFile(suInstance); super.start(suInstance); } @Override public void update(final Instance suInstance, final Import importChanged, final InstanceStatus statusChanged) throws PluginException { this.logger.fine( this.agentId + ": updating the " + this.getManagedArtifactType() + " for instance " + suInstance); this.updatePropertiesFile(suInstance); } private void updatePropertiesFile(final Instance suInstance) throws PluginException { final Instance componentInstance = suInstance.getParent(); this.logger.fine( this.agentId + ": updating the properties file for JBI component instance " + componentInstance); final String propertiesFileName = Utils.resolvePropertiesFileName( InstanceHelpers.findAllExportedVariables(componentInstance) .get(COMPONENT_VARIABLE_NAME_PROPERTIESFILE), this.retrieveContainerInstance(suInstance).getName(), componentInstance.getName()); this.logger.fine("Updated file: " + propertiesFileName); if (propertiesFileName != null && !propertiesFileName.isEmpty()) { final File propertiesFile = new File(propertiesFileName); if (!propertiesFile.exists() || (propertiesFile.exists() && propertiesFile.canWrite() && propertiesFile.isFile())) { // If the properties file exists, we read it final Properties properties = new Properties(); if (propertiesFile.exists()) { try (final InputStream fis = new FileInputStream(propertiesFile)) { properties.load(fis); } catch (final FileNotFoundException e) { // This exception should not occur because we have previously verified that the file exists this.logger.log(Level.WARNING, String.format("The properties file ('%s') of component '%s' no more exist", propertiesFileName, componentInstance.getName()), e); } catch (final IOException e) { throw new PluginException( String.format("Error reading the properties file ('%s') of component '%s'.", propertiesFileName, componentInstance.getName()), e); } } // Update property values with values coming from SU imports final Map<String, Collection<Import>> allImportedVariables = suInstance.getImports(); final Properties importedValues = new Properties(); for (final Entry<String, Collection<Import>> importEntry : allImportedVariables.entrySet()) { for (final Import anImport : importEntry.getValue()) { for (final Entry<String, String> anExportImported : anImport.getExportedVars().entrySet()) { importedValues.setProperty(anExportImported.getKey(), anExportImported.getValue()); } } } this.logger.fine(String.format("Imported variables used by the SU: %s", importedValues.toString())); final Map<String, String> allExportedVariables = InstanceHelpers .findAllExportedVariables(suInstance); this.logger.fine( String.format("All exported variables of the SU: %s", allExportedVariables.toString())); for (final Entry<String, String> entry : allExportedVariables.entrySet()) { // We must remove the SU component name implicitely added in exported variables final String keyName; if (entry.getKey().startsWith(suInstance.getComponent().getName())) { keyName = entry.getKey().substring(suInstance.getComponent().getName().length() + 1); } else { keyName = entry.getKey(); } // We resolved placeholder of the exported variable value with imported variables try { final String keyValue = PropertiesHelper.resolveString(entry.getValue(), importedValues); properties.setProperty(keyName, keyValue); } catch (final PropertiesException e) { throw new PluginException( String.format("Unable to resolve placeholder ('%s') of the SU '%s'.", entry.getValue(), suInstance.getName()), e); } } // Store the properties file Utils.storePropertiesFile(properties, propertiesFile, componentInstance.getName()); // Signal the component to reload its properties file try { this.connectToContainer(this.retrieveContainerInstance(suInstance)); try { this.adminApi.newArtifactAdministration().reloadConfiguration(componentInstance.getName()); } finally { this.adminApi.disconnect(); } } catch (final ContainerAdministrationException | ArtifactAdministrationException e) { throw new PluginException(e); } } else { throw new PluginException(String.format( "Unable to create or write into the properties file ('%s') of component '%s'.", propertiesFileName, componentInstance.getName())); } } } @Override public String getPluginName() { return PLUGIN_NAME; } @Override protected String getManagedArtifactType() { return "SA"; } @Override protected String getManagedArtifactName(final Instance suInstance) { return PluginPetalsSuInstaller.getSAName(suInstance); } @Override protected String getManagedArtifactVersion() { return null; } @Override protected final Instance retrieveContainerInstance(final Instance suInstance) throws PluginException { // TODO: Perhaps review how to retrieve container exported variables when // https://github.com/roboconf/roboconf-platform/issues/184 will be fixed final Instance seOrBcInstance = suInstance.getParent(); final Component seOrBcComponent = seOrBcInstance.getComponent().getExtendedComponent(); if (seOrBcComponent == null) { throw new PluginException( String.format("No parent defined for the Service Unit. It MUST be inherited from '%s' or '%s'", ROBOCONF_COMPONENT_SE_COMPONENT, ROBOCONF_COMPONENT_BC_COMPONENT)); } else if (!ROBOCONF_COMPONENT_SE_COMPONENT.equals(seOrBcComponent.getName()) && !ROBOCONF_COMPONENT_BC_COMPONENT.equals(seOrBcComponent.getName())) { throw new PluginException(String.format( "Unexpected parent for the Service Unit. It MUST be inherited from '%s' or '%s' (current '%s')", ROBOCONF_COMPONENT_SE_COMPONENT, ROBOCONF_COMPONENT_BC_COMPONENT, seOrBcComponent.getName())); } else { final Component jbiComponentComponent = seOrBcComponent.getExtendedComponent(); if (jbiComponentComponent == null) { throw new PluginException( String.format("No parent defined for the Service Unit. It MUST be inherited from '%s'", ROBOCONF_COMPONENT_ABTRACT_JBI_COMPONENT)); } else if (!ROBOCONF_COMPONENT_ABTRACT_JBI_COMPONENT.equals(jbiComponentComponent.getName())) { throw new PluginException(String.format( "Unexpected parent for the Service Unit. It MUST be inherited from '%s' (current '%s')", ROBOCONF_COMPONENT_ABTRACT_JBI_COMPONENT, jbiComponentComponent.getName())); } else { final Instance containerInstance = seOrBcInstance.getParent(); final Component jbiComponentAbstractContainer = containerInstance.getComponent() .getExtendedComponent(); if (jbiComponentAbstractContainer == null) { throw new PluginException(String.format( "Unexpected parent for the JBI component running the Service Unit. It MUST be inherited from '%s'", ROBOCONF_COMPONENT_ABTRACT_CONTAINER)); } else if (!ROBOCONF_COMPONENT_ABTRACT_CONTAINER.equals(jbiComponentAbstractContainer.getName())) { throw new PluginException(String.format( "Unexpected parent for the JBI component running the Service Unit. It MUST be inherited from '%s' (current '%s')", ROBOCONF_COMPONENT_ABTRACT_CONTAINER, jbiComponentAbstractContainer.getName())); } else { return containerInstance; } } } } protected static final String getSAName(final Instance instance) { return String.format(SA_NAME_TEMPLATE, instance.getName()); } }