/****************************************************************************** * Copyright (c) 2008-2013, Linagora * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Linagora - initial API and implementation *******************************************************************************/ package com.ebmwebsourcing.petals.common.generation; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * @author Vincent Zurczak - EBM WebSourcing */ public class JbiUtils { /** * Create a SU name from a service name. * <p> * This method does not add, check or modify file extensions. * </p> * * @param suType the component used by the SU (e.g. FTP, XSLT...) * @param serviceName the service name * @param isConsume true if the SU is <i>consume</i> mode, false for <i>provide</i> * @return the generated SU name */ public static String createSuName( String suType, String serviceName, boolean isConsume ) { String suMode = isConsume ? "consume" : "provide"; return "su-" + suType + "-" + serviceName + "-" + suMode; } /** * Create a SA name from a service name. * <p> * This method does not add, check or modify file extensions. * </p> * * @param suType the component used by the SU (e.g. FTP, XSLT...) * @param serviceName the service name * @param isConsume true if the SU is <i>consume</i> mode, false for <i>provide</i> * @return the generated SU name */ public static String createSaName( String suType, String serviceName, boolean isConsume ) { String suMode = isConsume ? "consume" : "provide"; return "sa-" + suType + "-" + serviceName + "-" + suMode; } /** * @param name a name to check * @return true if the name respects the SU naming convention, false otherwise */ public static boolean isSuName( String name ) { String token = "[a-zA-Z_]+[.\\w\\-]*"; return name.matches( "su-" + token + "-" + token + "-(consume|provide).*" ); } /** * @param name a name to check * @return true if the name respects the SA naming convention, false otherwise */ public static boolean isSaName( String name ) { String token = "[a-zA-Z_]+[.\\w\\-]*"; return name.matches( "sa-" + token + "-" + token + "-(consume|provide).*" ); } /** * @param name a name to check * @return true if the name starts with "sa-", false otherwise */ public static boolean isLightSaName( String name ) { return name.startsWith( "sa-" ); } /** * Creates a SA name from a SU name. * <p> * This method does not add, check or modify file extensions. * </p> * * @param suName a service unit archive name * @return the generated SU name */ public static String createSaName( String suName ) { if( suName == null ) return ""; if( suName.startsWith( "su-" )) return suName.replaceFirst( "su-", "sa-" ); return "sa-" + suName; } /** * Creates the jbi.xml content for a Petals SA. * * @param componentName the target component name * @param saName the SA name (not the service assembly file name). * <p> * Service assembly names look like <i>sa-Jsr181-<serviceName>-provide</i>. * </p> * @param suNames the SU names (not the service-unit file names). * <p> * Service unit names look like <i>su-Jsr181-<serviceName>-provide</i>. * </p> * @return the jbi.xml content */ public static String generateJbiXmlForSA( String componentName, String saName, String... suNames ) { // Content of the jbi.xml file StringBuilder sb = new StringBuilder(); sb.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ); sb.append( "<jbi:jbi version=\"1.0\"\n" ); sb.append( "\txmlns=\"http://java.sun.com/xml/ns/jbi\"\n" ); sb.append( "\txmlns:jbi=\"http://java.sun.com/xml/ns/jbi\">\n\n" ); sb.append( "\t<jbi:service-assembly>\n\t\t<jbi:identification>\n" ); sb.append( "\t\t\t<jbi:name>" + saName + "</jbi:name>\n" ); sb.append( "\t\t\t<jbi:description></jbi:description>\n" ); sb.append( "\t\t</jbi:identification>\n" ); for( String suName : suNames ) { sb.append( "\n\t\t<!-- New service-unit -->\n" ); sb.append( "\t\t<jbi:service-unit>\n\t\t\t<jbi:identification>\n" ); sb.append( "\t\t\t\t<jbi:name>" + suName + "</jbi:name>\n" ); sb.append( "\t\t\t\t<jbi:description></jbi:description>\n" ); sb.append( "\t\t\t</jbi:identification>\n\n" ); sb.append( "\t\t\t<jbi:target>\n" ); sb.append( "\t\t\t\t<jbi:artifacts-zip>" + suName + ".zip</jbi:artifacts-zip>\n" ); sb.append( "\t\t\t\t<jbi:component-name>" + componentName + "</jbi:component-name>\n" ); sb.append( "\t\t\t</jbi:target>\n\t\t</jbi:service-unit>\n" ); } sb.append( "\t</jbi:service-assembly>\n" ); sb.append( "</jbi:jbi>" ); return sb.toString(); } /** * Creates the jbi.xml content for a Petals SA. * * @param saName the SA name (not the service assembly file name). * <p> * Service assembly names look like <i>sa-Jsr181-<serviceName>-provide</i>. * </p> * @param suNameToComponentName the SU names, bound to the target component name * @return the jbi.xml content */ public static String generateJbiXmlForSA( String saName, Map<String,String> suNameToComponentName ) { // Content of the jbi.xml file StringBuilder sb = new StringBuilder(); sb.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ); sb.append( "<jbi:jbi version=\"1.0\"\n" ); sb.append( "\txmlns=\"http://java.sun.com/xml/ns/jbi\"\n" ); sb.append( "\txmlns:jbi=\"http://java.sun.com/xml/ns/jbi\">\n\n" ); sb.append( "\t<jbi:service-assembly>\n\t\t<jbi:identification>\n" ); sb.append( "\t\t\t<jbi:name>" + saName + "</jbi:name>\n" ); sb.append( "\t\t\t<jbi:description></jbi:description>\n" ); sb.append( "\t\t</jbi:identification>\n" ); for( Map.Entry<String,String> entry : suNameToComponentName.entrySet()) { sb.append( "\n\t\t<!-- New service-unit -->\n" ); sb.append( "\t\t<jbi:service-unit>\n\t\t\t<jbi:identification>\n" ); sb.append( "\t\t\t\t<jbi:name>" + entry.getKey() + "</jbi:name>\n" ); sb.append( "\t\t\t\t<jbi:description></jbi:description>\n" ); sb.append( "\t\t\t</jbi:identification>\n\n" ); sb.append( "\t\t\t<jbi:target>\n" ); sb.append( "\t\t\t\t<jbi:artifacts-zip>" + entry.getKey() + ".zip</jbi:artifacts-zip>\n" ); sb.append( "\t\t\t\t<jbi:component-name>" + entry.getValue() + "</jbi:component-name>\n" ); sb.append( "\t\t\t</jbi:target>\n\t\t</jbi:service-unit>\n" ); } sb.append( "\t</jbi:service-assembly>\n" ); sb.append( "</jbi:jbi>" ); return sb.toString(); } /** * Creates a service unit or a service assembly file. * * @param targetZipFile the path of the target ZIP file * @param jbiXmlContent the jbi.xml file content, or null to not add a jbi.xml * @param rootFolder the directory whose content must be zipped (the root folder won't be in the zip) * @return the created archive in case of success, null otherwise * @throws IOException */ public static File createJbiArchive( String targetZipFile, String jbiXmlContent, File rootFolder ) throws IOException { Map<String, File> entries = buildPackagingMap( rootFolder ); return createJbiArchive( targetZipFile, jbiXmlContent, entries ); } /** * Creates a service unit or a service assembly file. * * @param targetZipFile the path of the target ZIP file * @param rootFolder the folder containing all the resources (the first found jbi.xml will be set under META-INF/) * @return the created archive in case of success, null otherwise * @throws IOException */ public static File createJbiArchive( String targetZipFile, File rootFolder ) throws IOException { Map<String, File> entries = buildPackagingMap( rootFolder ); File f = entries.remove( "jbi.xml" ); if( f != null ) entries.put( "META-INF/jbi.xml", f ); return createJbiArchive( targetZipFile, null, entries ); } /** * Creates a service unit or a service assembly file. * * @param targetZipFile the path of the target ZIP file * @param entries key = zip entry, value = file to zip (not null) * @return the created archive in case of success, null otherwise * @throws IOException */ public static File createJbiArchive( String targetZipFile, Map<String, File> entries ) throws IOException { return createJbiArchive( targetZipFile, null, entries ); } /** * Creates a service unit or a service assembly file. * * @param targetZipFile the path of the target ZIP file * @param jbiXmlContent the jbi.xml file content, or null to not add a jbi.xml * @param entries key = zip entry, value = file to zip (not null) * @return the created archive in case of success, null otherwise * @throws IOException */ public static File createJbiArchive( String targetZipFile, String jbiXmlContent, Map<String, File> entries ) throws IOException { File zipFile = new File( targetZipFile ); File temporaryFile = File.createTempFile( "petalsTempFile-", null, null ); ZipOutputStream zos = new ZipOutputStream( new FileOutputStream( temporaryFile )); // Files for( Map.Entry<String, File> entry : entries.entrySet()) { if( entry.getValue().exists()) { InputStream in = new FileInputStream( entry.getValue()); addFileToZip( zos, in, entry.getKey()); } } // JBI descriptor if( jbiXmlContent != null ) { ByteArrayInputStream in = new ByteArrayInputStream( jbiXmlContent.getBytes()); addFileToZip( zos, in, "META-INF/jbi.xml" ); } zos.close(); if( zipFile.exists()) { if( ! zipFile.delete()) throw new IOException( "Could not delete existing target destination." ); } if( ! temporaryFile.renameTo( zipFile )) { // Could not move the file, try to copy it copyFile( temporaryFile, zipFile ); if( ! temporaryFile.delete()) temporaryFile.deleteOnExit(); } return zipFile; } /** * Adds a file into a zip archive. * * @param out the zip file * @param in the input stream to add into the zip * @param entry the entry for the file to add into the zip (not null) * @throws IOException */ private static void addFileToZip( ZipOutputStream out, InputStream in, String entry ) throws IOException { byte[] buf = new byte[ 1024 ]; // Create a buffer for reading the files ZipEntry ze = new ZipEntry( entry ); try { out.putNextEntry( ze ); int len; while ((len = in.read( buf )) > 0) { out.write( buf, 0, len); } } finally { try { in.close (); } finally { out.closeEntry(); } } } /** * Creates a map used to map files and zip entries. * @param root the directory whose content must be zipped * @return a map (key = zip entry, value = file to zip). * <p> * The result of this method can be passed as an argument to * {@link #createJbiArchive(String, Map)} and * {@link #createJbiArchive(String, String, Map)}. * </p> */ public static Map<String,File> buildPackagingMap( File root ) { return addRecursiveFiles( root, "" ); } /** * Adds recursively entries into the zip file. * <p> * The root file is not added in the map, just its children files.<br /> * Hidden files are skipped and are not added in the result. * </p> * * @param root the root file * @param entry the zip entry (not null) * @return */ private static Map<String,File> addRecursiveFiles( File root, String entry ) { Map<String,File> result = new HashMap<String, File> (); for( File file : root.listFiles()) { if( file.isHidden() || file.getName().startsWith( "." )) continue; if( entry.trim().length() != 0 && ! entry.endsWith( "/" )) entry += "/"; if( ! file.isDirectory()) { result.put( entry + file.getName(), file ); } else { Map<String,File> subResult = addRecursiveFiles( file, entry + file.getName() ); result.putAll( subResult ); } } return result; } /** * Copies the content from inputFile into outputFile. * * @param inputFile * @param outputFile will be created if it does not exist * @throws IOException */ private static void copyFile( File inputFile, File outputFile ) throws IOException { InputStream is = null; OutputStream os = null; try { is = new FileInputStream( inputFile ); if( ! outputFile.exists() && ! outputFile.createNewFile()) throw new IOException( "Failed to create " + outputFile.getAbsolutePath() + "." ); os = new FileOutputStream( outputFile ); byte[] buf = new byte[ 1024 ]; int len; while((len = is.read( buf )) > 0) { os.write( buf, 0, len ); } } finally { if( os != null ) { try { os.close(); } catch( Exception e ) { // nothing } } if( is != null ) is.close(); } } }