| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.modules.autoupdate.services; |
| |
| import java.beans.PropertyVetoException; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.*; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.netbeans.Module; |
| import org.netbeans.api.autoupdate.OperationException; |
| import org.netbeans.api.autoupdate.UpdateElement; |
| import org.netbeans.core.startup.MainLookup; |
| import org.netbeans.core.startup.layers.LocalFileSystemEx; |
| import org.netbeans.spi.autoupdate.AutoupdateClusterCreator; |
| import org.netbeans.updater.ModuleDeactivator; |
| import org.netbeans.updater.UpdateTracking; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.modules.InstalledFileLocator; |
| import org.openide.util.Lookup; |
| import org.openide.util.lookup.ServiceProvider; |
| |
| /** |
| * |
| * @author Jiri Rechtacek |
| */ |
| @ServiceProvider(service=InstalledFileLocator.class) |
| public class InstallManager extends InstalledFileLocator{ |
| |
| // special directories in NB files layout |
| static final String NBM_LIB = "lib"; // NOI18N |
| static final String NBM_CORE = "core"; // NOI18N |
| static final String NETBEANS_DIRS = "netbeans.dirs"; // NOI18N |
| |
| private static int countOfWarnings = 0; |
| private static final int MAX_COUNT_OF_WARNINGS = 5; |
| |
| private static final Logger ERR = Logger.getLogger ("org.netbeans.modules.autoupdate.services.InstallManager"); |
| private static final List<File> clusters = new ArrayList<File>(); |
| |
| static File findTargetDirectory (UpdateElement installed, UpdateElementImpl update, Boolean globalOrLocal, boolean useUserdirAsFallback) throws OperationException { |
| File res; |
| if (globalOrLocal == null) { |
| globalOrLocal = isGlobalInstallation(); |
| } |
| boolean isGlobal = globalOrLocal == null ? false : globalOrLocal; |
| |
| if (Boolean.FALSE.equals(globalOrLocal)) { |
| ERR.log(Level.INFO, "Forced installation in userdir only for " + update.getUpdateElement()); |
| return getUserDir(); |
| } |
| |
| // if an update, overwrite the existing location, wherever that is. |
| if (installed != null) { |
| |
| // adjust isGlobal to forced global if present |
| isGlobal |= update.getInstallInfo ().isGlobal () != null && update.getInstallInfo ().isGlobal (); |
| res = getInstallDir (installed, update, isGlobal, useUserdirAsFallback); |
| |
| } else { |
| |
| // #111384: fixed modules must be installed globally |
| isGlobal |= update.isFixed (); |
| |
| // adjust isGlobal to forced global if present |
| isGlobal |= update.getInstallInfo ().isGlobal () != null && update.getInstallInfo ().isGlobal (); |
| |
| final String targetCluster = update.getInstallInfo ().getTargetCluster (); |
| |
| // global or local |
| if ((targetCluster != null && targetCluster.length () > 0) || isGlobal) { |
| res = checkTargetCluster(update, targetCluster, isGlobal, useUserdirAsFallback); |
| |
| // handle non-existing clusters |
| if (res == null && targetCluster != null) { |
| res = createNonExistingCluster (targetCluster); |
| if (res != null) { |
| res = checkTargetCluster(update, targetCluster, isGlobal, useUserdirAsFallback); |
| } |
| } |
| |
| // target cluster still not found |
| if (res == null) { |
| |
| // create UpdateTracking.EXTRA_CLUSTER_NAME |
| createNonExistingCluster (UpdateTracking.EXTRA_CLUSTER_NAME); |
| // check writable installation |
| res = checkTargetCluster(update, UpdateTracking.EXTRA_CLUSTER_NAME, isGlobal, useUserdirAsFallback); |
| |
| // no new cluster was created => use userdir |
| res = res == null? getUserDir () : res; |
| |
| if (targetCluster != null) { |
| ERR.log (Level.INFO, "Declared target cluster " + targetCluster + |
| " in " + update.getUpdateElement () + " wasn't found or was read only. Will be used " + res); |
| } else { |
| ERR.log (Level.INFO, res + " will be used as target cluster"); |
| } |
| |
| } |
| |
| } else { |
| // is local |
| res = getUserDir (); |
| } |
| } |
| ERR.log (Level.FINEST, "UpdateElement " + update.getUpdateElement () + " has the target cluster " + res); |
| return res; |
| } |
| |
| private static File checkTargetCluster(UpdateElementImpl update, String targetCluster, boolean isGlobal, boolean useUserdirAsFallback) throws OperationException { |
| if (targetCluster == null || targetCluster.length () == 0) { |
| return null; |
| } |
| File res = null; |
| // is global or |
| // does have a target cluster? |
| for (File cluster : UpdateTracking.clusters (true)) { |
| if (targetCluster.equals (cluster.getName ())) { |
| boolean wasNew = ! cluster.exists (); |
| if (Utilities.canWriteInCluster (cluster)) { |
| if (wasNew) { |
| cluster.mkdirs (); |
| extendSystemFileSystem (cluster); |
| } |
| res = cluster; |
| } else { |
| if (! useUserdirAsFallback && isGlobal) { |
| ERR.log(Level.WARNING, "There is no write permission to write in target cluster " + targetCluster + " for " + update.getUpdateElement()); |
| throw new OperationException(OperationException.ERROR_TYPE.WRITE_PERMISSION, update.getCodeName()); |
| } |
| if (countOfWarnings++ < MAX_COUNT_OF_WARNINGS) { |
| ERR.log(Level.WARNING, "There is no write permission to write in target cluster " + targetCluster + " for " + update.getUpdateElement()); |
| } |
| if (countOfWarnings == MAX_COUNT_OF_WARNINGS) { |
| ERR.log(Level.WARNING, "There is no write permission to write in target cluster " + targetCluster + " for more updates or plugins."); |
| } |
| } |
| break; |
| } |
| } |
| |
| return res; |
| } |
| |
| private static File createNonExistingCluster (String targetCluster) { |
| File res = null; |
| for (AutoupdateClusterCreator creator : Lookup.getDefault ().lookupAll (AutoupdateClusterCreator.class)) { |
| File possibleCluster = Trampoline.SPI.findCluster (targetCluster, creator); |
| if (possibleCluster != null) { |
| try { |
| ERR.log (Level.FINE, "Found cluster candidate " + possibleCluster + " for declared target cluster " + targetCluster); |
| File[] dirs = Trampoline.SPI.registerCluster (targetCluster, possibleCluster, creator); |
| |
| // it looks good, generate new netbeans.dirs |
| res = possibleCluster; |
| |
| StringBuffer sb = new StringBuffer (); |
| String sep = ""; |
| for (File dir : dirs) { |
| sb.append (sep); |
| sb.append(dir.getPath()); |
| sep = File.pathSeparator; |
| } |
| |
| System.setProperty(NETBEANS_DIRS, sb.toString ()); |
| File f = new File(new File(getUserDir(), Utilities.DOWNLOAD_DIR), NETBEANS_DIRS); |
| if (!f.exists()) { |
| f.getParentFile().mkdirs(); |
| f.createNewFile(); |
| } |
| OutputStream os = new FileOutputStream(f); |
| try { |
| os.write(sb.toString().getBytes()); |
| } finally { |
| os.close(); |
| } |
| ERR.log (Level.FINE, "Was written new netbeans.dirs " + sb); |
| |
| break; |
| |
| } catch (IOException ioe) { |
| ERR.log (Level.INFO, ioe.getMessage (), ioe); |
| } |
| } |
| } |
| return res; |
| } |
| |
| private static void extendSystemFileSystem(File cluster) { |
| try { |
| File extradir = new File(cluster, ModuleDeactivator.CONFIG); |
| extradir.mkdir(); |
| LocalFileSystemEx lfse = new LocalFileSystemEx(); |
| lfse.setRootDirectory(extradir); |
| MainLookup.register(lfse); |
| synchronized (InstallManager.class) { |
| clusters.add(cluster); |
| } |
| } catch (PropertyVetoException ioe) { |
| ERR.log (Level.INFO, ioe.getMessage (), ioe); |
| } catch (IOException ioe) { |
| ERR.log (Level.INFO, ioe.getMessage (), ioe); |
| } |
| } |
| |
| // can be null for fixed modules |
| private static File getInstallDir (UpdateElement installed, UpdateElementImpl update, boolean isGlobal, boolean useUserdirAsFallback) throws OperationException { |
| File res = null; |
| UpdateElementImpl i = Trampoline.API.impl (installed); |
| assert i instanceof ModuleUpdateElementImpl : "Impl of " + installed + " instanceof ModuleUpdateElementImpl"; |
| |
| Module m = Utilities.toModule (((ModuleUpdateElementImpl) i).getModuleInfo ()); |
| File jarFile = m == null ? null : m.getJarFile (); |
| |
| if (jarFile == null) { |
| // only fixed module cannot be located |
| ERR.log (Level.FINE, "No install dir for " + installed + " (It's ok for fixed). Is fixed? " + Trampoline.API.impl (installed).isFixed ()); |
| String targetCluster = update.getInstallInfo ().getTargetCluster (); |
| if (targetCluster != null) { |
| for (File cluster : UpdateTracking.clusters (false)) { |
| if (targetCluster.equals (cluster.getName ())) { |
| res = cluster; |
| break; |
| } |
| } |
| } |
| if (res == null) { |
| // go to platform if no cluster is known |
| res = UpdateTracking.getPlatformDir (); |
| } |
| } else { |
| |
| /* comment out for xtesting |
| FileObject searchForFO = FileUtil.toFileObject (configFile); |
| for (File cluster : UpdateTracking.clusters (true)) { |
| cluster = FileUtil.normalizeFile(cluster); |
| if (FileUtil.isParentOf (FileUtil.toFileObject (cluster), searchForFO)) { |
| res = cluster; |
| break; |
| }*/ |
| |
| for (File cluster : UpdateTracking.clusters (true)) { |
| cluster = FileUtil.normalizeFile (cluster); |
| if (isParentOf (cluster, jarFile)) { |
| res = cluster; |
| break; |
| } |
| } |
| } |
| |
| if (res == null || ! Utilities.canWriteInCluster (res)) { |
| if (! useUserdirAsFallback && isGlobal) { |
| ERR.log(Level.WARNING, "There is no write permission to write in target cluster " + res + " for " + update.getUpdateElement()); |
| throw new OperationException(OperationException.ERROR_TYPE.WRITE_PERMISSION, update.getCodeName()); |
| } |
| // go to userdir if no writable cluster is known |
| if (countOfWarnings++ < MAX_COUNT_OF_WARNINGS) { |
| ERR.log(Level.WARNING, "There is no write permission to write in target cluster " + res + " for " + update.getUpdateElement()); |
| } |
| if (countOfWarnings == MAX_COUNT_OF_WARNINGS) { |
| ERR.log(Level.WARNING, "There is no write permission to write in target cluster " + res + " for more updates or plugins."); |
| } |
| res = UpdateTracking.getUserDir (); |
| } |
| ERR.log (Level.FINEST, "Install dir of " + installed + " is " + res); |
| |
| return res; |
| } |
| |
| private static boolean isParentOf (File parent, File child) { |
| File tmp = child.getParentFile (); |
| while (tmp != null && ! parent.equals (tmp)) { |
| tmp = tmp.getParentFile (); |
| } |
| return tmp != null; |
| } |
| |
| static File getUserDir () { |
| return UpdateTracking.getUserDir (); |
| } |
| |
| static boolean needsRestart (boolean isUpdate, UpdateElementImpl update, File dest) { |
| assert update.getInstallInfo () != null : "Each UpdateElement must know own InstallInfo but " + update; |
| boolean isForcedRestart = update.getInstallInfo ().needsRestart () != null && update.getInstallInfo ().needsRestart (); |
| boolean needsRestart = isForcedRestart || isUpdate; |
| if (! needsRestart) { |
| // handle installation into core or lib directory |
| needsRestart = willInstallInSystem (dest); |
| } |
| return needsRestart; |
| } |
| |
| private static boolean willInstallInSystem (File nbmFile) { |
| boolean res = false; |
| try { |
| JarFile jf = new JarFile (nbmFile); |
| try { |
| for (JarEntry entry : Collections.list (jf.entries ())) { |
| String entryName = entry.getName (); |
| if (entryName.startsWith (NBM_CORE + "/") || entryName.startsWith (NBM_LIB + "/")) { |
| res = true; |
| break; |
| } |
| } |
| } finally { |
| jf.close(); |
| } |
| } catch (IOException ioe) { |
| ERR.log (Level.INFO, ioe.getMessage (), ioe); |
| } |
| |
| return res; |
| } |
| |
| @Override |
| public File locate(String relativePath, String codeNameBase, boolean localized) { |
| // Rarely returns anything so don't bother optimizing. |
| Set<File> files = locateAll(relativePath, codeNameBase, localized); |
| return files.isEmpty() ? null : files.iterator().next(); |
| } |
| |
| public @Override Set<File> locateAll(String relativePath, String codeNameBase, boolean localized) { |
| synchronized (InstallManager.class) { |
| if (clusters.isEmpty()) { |
| return Collections.<File>emptySet(); |
| } |
| } |
| // XXX #28729: use codeNameBase to search only in the appropriate places |
| if (relativePath.length() == 0) { |
| throw new IllegalArgumentException("Cannot look up \"\" in InstalledFileLocator.locate"); // NOI18N |
| } |
| if (relativePath.charAt(0) == '/') { |
| throw new IllegalArgumentException("Paths passed to InstalledFileLocator.locate should not start with '/': " + relativePath); // NOI18N |
| } |
| int slashIdx = relativePath.lastIndexOf('/'); |
| if (slashIdx == relativePath.length() - 1) { |
| throw new IllegalArgumentException("Paths passed to InstalledFileLocator.locate should not end in '/': " + relativePath); // NOI18N |
| } |
| |
| String prefix, name; |
| if (slashIdx != -1) { |
| prefix = relativePath.substring(0, slashIdx + 1); |
| name = relativePath.substring(slashIdx + 1); |
| assert name.length() > 0; |
| } else { |
| prefix = ""; |
| name = relativePath; |
| } |
| if (localized) { |
| int i = name.lastIndexOf('.'); |
| String baseName, ext; |
| if (i == -1) { |
| baseName = name; |
| ext = ""; |
| } else { |
| baseName = name.substring(0, i); |
| ext = name.substring(i); |
| } |
| String[] suffixes = org.netbeans.Util.getLocalizingSuffixesFast(); |
| Set<File> files = new HashSet<File>(); |
| for (String suffixe : suffixes) { |
| String locName = baseName + suffixe + ext; |
| files.addAll(locateExactPath(prefix, locName)); |
| } |
| return files; |
| } else { |
| return locateExactPath(prefix, name); |
| } |
| |
| } |
| |
| /** Search all top dirs for a file. */ |
| private static Set<File> locateExactPath(String prefix, String name) { |
| Set<File> files = new HashSet<File>(); |
| synchronized(InstallManager.class) { |
| File[] dirs = clusters.toArray(new File[clusters.size()]); |
| for (File dir : dirs) { |
| File f = makeFile(dir, prefix, name); |
| if (f.exists()) { |
| files.add(f); |
| } |
| } |
| } |
| return files; |
| } |
| |
| private static File makeFile(File dir, String prefix, String name) { |
| return FileUtil.normalizeFile(new File(dir, prefix.replace('/', File.separatorChar) + name)); |
| } |
| |
| private static Boolean isGlobalInstallation() { |
| String s = System.getProperty("plugin.manager.install.global"); // NOI18N |
| |
| if (Boolean.parseBoolean(s)) { |
| return Boolean.TRUE; |
| } else if (Boolean.FALSE.toString().equalsIgnoreCase(s)) { |
| return Boolean.FALSE; |
| } else { |
| return null; |
| } |
| } |
| |
| } |