001/** 002 * Copyright (C) 2006-2024 Talend Inc. - www.talend.com 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.talend.sdk.component.tools; 017 018import static java.util.Optional.ofNullable; 019import static java.util.stream.Collectors.toList; 020 021import java.io.BufferedOutputStream; 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.FileOutputStream; 025import java.io.FileReader; 026import java.io.FileWriter; 027import java.io.IOException; 028import java.io.InputStream; 029import java.io.OutputStream; 030import java.io.Reader; 031import java.io.Writer; 032import java.nio.file.Files; 033import java.nio.file.StandardOpenOption; 034import java.time.LocalDateTime; 035import java.time.format.DateTimeFormatter; 036import java.util.List; 037import java.util.Map; 038import java.util.Properties; 039import java.util.stream.Stream; 040 041import org.talend.sdk.component.dependencies.maven.Artifact; 042import org.talend.sdk.component.dependencies.maven.MvnCoordinateToFileConverter; 043 044public class StudioInstaller implements Runnable { 045 046 private final String mainGav; 047 048 private final File studioHome; 049 050 private final Map<String, File> artifacts; 051 052 private final Log log; 053 054 private final boolean enforceDeployment; 055 056 private final File m2; 057 058 public StudioInstaller(final String mainGav, final File studioHome, final Map<String, File> artifacts, 059 final Object log, final boolean enforceDeployment, final File m2) { 060 this.mainGav = mainGav; 061 this.studioHome = studioHome; 062 this.artifacts = artifacts; 063 this.m2 = m2; 064 this.enforceDeployment = enforceDeployment; 065 try { 066 this.log = Log.class.isInstance(log) ? Log.class.cast(log) : new ReflectiveLog(log); 067 } catch (final NoSuchMethodException e) { 068 throw new IllegalStateException(e); 069 } 070 } 071 072 @Override 073 public void run() { 074 log.info("Installing development version of " + mainGav + " in " + studioHome); 075 076 final List<String> artifacts = this.artifacts.values().stream().map(File::getName).collect(toList()); 077 final String mvnMeta[] = mainGav.split(":"); 078 final String artifact = mvnMeta[1]; 079 final String version = mvnMeta[2]; 080 081 // 0. check if component can be installed. 082 final File configIni = new File(studioHome, "configuration/config.ini"); 083 final Properties config = new Properties(); 084 if (configIni.exists()) { 085 try (final InputStream is = new FileInputStream(configIni)) { 086 config.load(is); 087 } catch (final IOException e) { 088 throw new IllegalStateException(e); 089 } 090 } 091 String registry = config 092 .getProperty("component.java.registry", 093 config.getProperty("talend.component.server.component.registry")); 094 if (!enforceDeployment && registry != null && new File(registry).exists()) { 095 final Properties components = new Properties(); 096 try (final Reader reader = new FileReader(registry)) { 097 components.load(reader); 098 if (components.containsKey(artifact)) { 099 final String installedVersion = components.getProperty(artifact).split(":")[2]; 100 if (!version.equals(installedVersion)) { 101 throw new IllegalStateException("Can't deploy this component. A different version '" 102 + installedVersion 103 + "' is already installed.\nYou can enforce the deployment by using -Dtalend.component.enforceDeployment=true"); 104 } 105 } 106 } catch (final IOException e) { 107 log.error("Can't load registered component from the studio " + e.getMessage()); 108 } 109 } 110 111 // 1. remove staled libs from the cache (configuration/org.eclipse.osgi) 112 final File osgiCache = new File(studioHome, "configuration/org.eclipse.osgi"); 113 if (osgiCache.isDirectory()) { 114 ofNullable(osgiCache.listFiles(child -> { 115 try { 116 return child.isDirectory() && Integer.parseInt(child.getName()) > 0; 117 } catch (final NumberFormatException nfe) { 118 return false; 119 } 120 })) 121 .map(Stream::of) 122 .orElseGet(Stream::empty) 123 .map(id -> new File(id, ".cp")) 124 .filter(File::exists) 125 .flatMap(cp -> ofNullable(cp.listFiles((dir, name) -> name.endsWith(".jar"))) 126 .map(Stream::of) 127 .orElseGet(Stream::empty)) 128 .filter(jar -> artifacts.contains(jar.getName())) 129 .forEach(this::tryDelete); 130 } 131 132 // 2. install the runtime dependency tree (scope compile+runtime) in the studio m2 repo 133 final String repoType = config.getProperty("maven.repository"); 134 if (!"global".equals(repoType)) { 135 final MvnCoordinateToFileConverter converter = new MvnCoordinateToFileConverter(); 136 this.artifacts.forEach((gav, file) -> { 137 final Artifact dependency = converter.toArtifact(gav); 138 final File target = m2 == null || !m2.exists() 139 ? new File(studioHome, "configuration/.m2/repository/" + dependency.toPath()) 140 : new File(m2, dependency.toPath()); 141 try { 142 if (target.exists() && !dependency.getVersion().endsWith("-SNAPSHOT")) { 143 log.info(gav + " already exists, skipping"); 144 return; 145 } 146 copy(file, target); 147 log.info("Installed " + gav + " at " + target.getAbsolutePath()); 148 } catch (final IOException e) { 149 throw new IllegalStateException(e); 150 } 151 }); 152 } else { 153 log 154 .info("Studio " + studioHome 155 + " configured to use global maven repository, skipping artifact installation"); 156 } 157 158 // 3. register component adding them into the registry 159 if (registry == null) { 160 final File registryLocation = new File(configIni.getParentFile(), "components-registration.properties"); 161 registryLocation.getParentFile().mkdirs(); 162 registry = registryLocation.getAbsolutePath().replace('\\', '/'); 163 164 config.setProperty("component.java.registry", registry); 165 try { 166 final File backup = new File(configIni.getParentFile(), "backup/" + configIni.getName() + "_" 167 + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-mm-dd_HH-mm-ss"))); 168 log.info("Saving configuration in " + backup); 169 copy(configIni, backup); 170 } catch (final IOException e) { 171 throw new IllegalStateException(e); 172 } 173 try (final Writer writer = new FileWriter(configIni)) { 174 config 175 .store(writer, "File rewritten by " + getClass().getName() 176 + " utility to add component.java.registry entry"); 177 log.info("Updated " + configIni + " to add the component registry entry"); 178 } catch (final IOException e) { 179 throw new IllegalStateException(e); 180 } 181 try { 182 Files.write(registryLocation.toPath(), new byte[0], StandardOpenOption.CREATE_NEW); 183 } catch (final IOException e) { 184 throw new IllegalStateException(e); 185 } 186 } 187 final Properties components = new Properties(); 188 try (final Reader reader = new FileReader(registry)) { 189 components.load(reader); 190 } catch (final IOException e) { 191 throw new IllegalStateException(e); 192 } 193 194 if (!components.containsKey(artifact) || enforceDeployment) { 195 components.setProperty(artifact, mainGav); 196 try (final Writer writer = new FileWriter(registry)) { 197 components.store(writer, "File rewritten to add " + mainGav); 198 log.info("Updated " + registry + " with '" + mainGav + "'"); 199 } catch (final IOException e) { 200 throw new IllegalStateException(e); 201 } 202 } 203 } 204 205 private void copy(final File source, final File target) throws IOException { 206 mkdirP(target.getParentFile()); 207 try (final OutputStream to = new BufferedOutputStream(new FileOutputStream(target))) { 208 Files.copy(source.toPath(), to);// this copy don't lock on win 209 } 210 } 211 212 private void mkdirP(final File dir) { 213 if (!dir.exists() && !dir.mkdirs()) { 214 throw new IllegalStateException("Can't create " + dir); 215 } 216 } 217 218 private void tryDelete(final File jar) { 219 if (!jar.delete()) { 220 log.error("Can't delete " + jar.getAbsolutePath()); 221 } else { 222 log.info("Deleting " + jar.getAbsolutePath()); 223 } 224 } 225 226}