Java tutorial
/* * Copyright (C) 2015 sauerkraut.to <gutsverwalter@sauerkraut.to> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package to.sauerkraut.krautadmin.core; import com.google.common.base.Strings; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.security.CodeSource; import java.security.Permission; import java.security.PermissionCollection; import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javassist.CannotCompileException; import javassist.CtClass; import javassist.CtMethod; import javassist.NotFoundException; import jd.plugins.PluginForHost; import org.apache.commons.io.FileUtils; import org.appwork.exceptions.WTFException; import org.appwork.shutdown.ShutdownController; import org.appwork.shutdown.ShutdownEvent; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.RebaseResult; import org.eclipse.jgit.api.RebaseResult.Status; import org.eclipse.jgit.api.ResetCommand; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.merge.MergeStrategy; import org.jdownloader.plugins.controller.PluginClassLoader; import org.jdownloader.plugins.controller.UpdateRequiredClassNotFoundException; import org.jdownloader.plugins.controller.host.HostPluginController; import org.jdownloader.plugins.controller.host.LazyHostPlugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import to.sauerkraut.jgitclone.api.commands.GitClone; import to.sauerkraut.jgitclone.api.commands.GitCloneOptions; import to.sauerkraut.jgitclone.utilities.UrlUtilities; import to.sauerkraut.krautadmin.KrautAdminApplication; import javax.validation.ConstraintViolation; /** * * @author sauerkraut.to <gutsverwalter@sauerkraut.to> */ @SuppressWarnings("checkstyle:classfanoutcomplexity") public final class Toolkit { public static final String LINK_CHECKER_UPDATES_GIT_URL = "https://github.com/sauerkraut-to/jdupdates.git"; public static final String LINK_CHECKER_DOWNLOAD_FOLDER_NAME = "jd"; private static final Logger LOG = LoggerFactory.getLogger(Toolkit.class); private Toolkit() { } public static void setAssertionsEnabled(final boolean enabled) { setAssertionsEnabled(enabled, ClassLoader.getSystemClassLoader()); } public static <T> String join(final Iterable<T> values, final String separator) { if (values == null) { return ""; } final Iterator<T> iter = values.iterator(); if (!iter.hasNext()) { return ""; } final StringBuffer toReturn = new StringBuffer(String.valueOf(iter.next())); while (iter.hasNext()) { toReturn.append(separator + String.valueOf(iter.next())); } return toReturn.toString(); } public static String join(final String[] values, final String separator) { return (values == null) ? "" : join(Arrays.asList(values), separator); } public static void kill(final String pid) throws Exception { final String os = System.getProperty("os.name"); final String command = (os.startsWith("Windows")) ? "taskkill /F /PID " + pid : "kill " + pid; Runtime.getRuntime().exec(command).waitFor(); } public static void setAssertionsEnabled(final boolean enabled, final ClassLoader classLoader) { classLoader.setDefaultAssertionStatus(enabled); } public static void setFinalStaticField(final Field field, final Object newValue) throws Exception { setFinalPrivateField(field, null, newValue); } public static void setFinalPrivateField(final Field field, final Object instance, final Object newValue) throws Exception { field.setAccessible(true); final Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(instance, newValue); } public static void logConstraintViolations(final Set<ConstraintViolation<?>> constraintViolations) { for (ConstraintViolation violation : constraintViolations) { final String propertyPath = violation.getPropertyPath().toString(); final String message = violation.getMessage(); final String className = violation.getRootBeanClass().getCanonicalName(); LOG.error("fixture constraint violation - propertyPath: {} " + "- message: {} - className: {}", propertyPath, message, className); } } public static synchronized void restartApplication() throws Exception { //TODO: implement } public static void setPrivateField(final Field field, final Object instance, final Object newValue) throws Exception { field.setAccessible(true); field.set(instance, newValue); } public static void setPrivateStaticField(final Field field, final Object newValue) throws Exception { setPrivateField(field, null, newValue); } public static void modifyByteCode(final CtClass ctClass, final String methodName, final CtClass[] methodParams, final String methodBody, final boolean loadAfter, final boolean writeClass) throws NotFoundException, CannotCompileException, IOException { // get the method from the Class byte code final CtMethod method = ctClass.getDeclaredMethod(methodName, methodParams); // set new method body method.setBody(methodBody); if (writeClass) { // class-file replacement ctClass.writeFile(); } if (loadAfter) { ctClass.toClass(); } } public static void modifyByteCode(final CtClass ctClass, final String methodName, final CtClass[] methodParams, final String methodBody, final boolean loadAfter) throws NotFoundException, CannotCompileException, IOException { modifyByteCode(ctClass, methodName, methodParams, methodBody, loadAfter, false); } public static String getApplicationContainingFolder() throws Exception { return getApplicationContainingFolder(KrautAdminApplication.class); } private static String getApplicationContainingFolder(final Class aClass) throws Exception { final File jarFile = getPossibleApplicationJarFile(aClass); return jarFile.getParentFile().getAbsolutePath(); } /** * * @return null, if the application is not delivered as a fat .jar */ public static String getApplicationJarName() throws IOException { final File possibleJarFile = getPossibleApplicationJarFile(KrautAdminApplication.class); if (possibleJarFile != null && possibleJarFile.getName().toUpperCase().endsWith(".JAR")) { return possibleJarFile.getName(); } else { return null; } } public static VirtualFile getVirtualFile(final String applicationRelativePath) { return VirtualFile.fromApplicationRelativePath(applicationRelativePath); } private static File getPossibleApplicationJarFile(final Class aClass) throws IOException { final CodeSource codeSource = aClass.getProtectionDomain().getCodeSource(); File jarFile; if (codeSource.getLocation() != null) { try { jarFile = new File(codeSource.getLocation().toURI()); } catch (URISyntaxException e) { return null; } } else { final String path = aClass.getResource(aClass.getSimpleName() + ".class").getPath(); String jarFilePath = path.substring(path.indexOf(':') + 1, path.indexOf('!')); jarFilePath = URLDecoder.decode(jarFilePath, "UTF-8"); jarFile = new File(jarFilePath); } return jarFile; } public static String parseDbPath(final String path) { final String trimmedPath = Strings.emptyToNull(path); return trimmedPath == null ? null : trimmedPath.replace("$TMP", System.getProperty("java.io.tmpdir")).replace("$APP", KrautAdminApplication.getApplicationContainingFolder()); } /** * Delete all downloaded link checker classes. * It will force the next iteration of the link checker update cronjob to fully re-download the latest set * of link checker classes. * @throws IOException if the directory containing the link checker classes couldn't be deleted */ public static synchronized void clearLinkCheckers() throws IOException { FileUtils.deleteDirectory(new File(KrautAdminApplication.getApplicationContainingFolder() .concat(File.separator.concat(LINK_CHECKER_DOWNLOAD_FOLDER_NAME)))); } /** * Validates a Zip archive. * If the archive is invalid, cannot be opened or integrity errors are detected, * Exceptions will be thrown. * @param zipFile * @throws Exception */ public static void validateZipFile(final File zipFile) throws IOException { final ZipFile applicationUpdateJar = new ZipFile(zipFile); final Enumeration<? extends ZipEntry> applicationUpdateJarEntries = applicationUpdateJar.entries(); // iterating through the .jar will detect integrity errors of the zip archive while (applicationUpdateJarEntries.hasMoreElements()) { applicationUpdateJarEntries.nextElement().getName(); } } public static synchronized void updateLinkCheckers() throws Exception { final File pluginsParentDirectory = new File(KrautAdminApplication.getApplicationContainingFolder() .concat(File.separator).concat(LINK_CHECKER_DOWNLOAD_FOLDER_NAME)); boolean needsClassesReload = false; final URI linkCheckerUpdatesGitUri = new URI(LINK_CHECKER_UPDATES_GIT_URL); // try incremental update at first - if it fails, try a whole new repo clone try { needsClassesReload = updateFromGit(pluginsParentDirectory, linkCheckerUpdatesGitUri); } catch (Exception e) { if (e instanceof WTFException) { LOG.error(e.getMessage(), e); } else { LOG.info(e.getMessage()); } cloneFromGit(pluginsParentDirectory, linkCheckerUpdatesGitUri, true); needsClassesReload = true; } if (needsClassesReload) { reloadLinkCheckerClasses(); } } public static void cloneFromGit(final File targetDirectory, final URI repositoryUri, final boolean shallow) throws Exception { try { FileUtils.deleteDirectory(targetDirectory); FileUtils.forceMkdir(targetDirectory); } catch (IOException ioex) { LOG.info("Could not delete and/or recreate plugin git repo directory, maybe did not exist"); } LOG.info("starting full plugin-update from git (shallow: " + String.valueOf(shallow) + ") ..."); if (shallow) { final GitClone gitClone = new GitClone(); final GitCloneOptions gitCloneOptions = new GitCloneOptions(); gitCloneOptions.setDepth(2); gitClone.clone(targetDirectory, gitCloneOptions, UrlUtilities.url2JavaGitUrl(repositoryUri.toURL()), targetDirectory); } else { Git.cloneRepository().setURI(repositoryUri.toString()).setDirectory(targetDirectory).call(); } LOG.info("full plugin-update successful"); } public static boolean updateFromGit(final File repositoryDirectory, final URI repositoryUri) throws Exception { final String unexpectedExceptionText = "incremental plugin-update from git failed"; final String upstream = "refs/remotes/origin/master"; boolean hasUpdated = false; Git gitRepo = null; try { gitRepo = Git.open(repositoryDirectory); // first reset local changes gitRepo.reset().setMode(ResetCommand.ResetType.HARD).call(); LOG.info("starting incremental plugin-update from git..."); gitRepo.fetch().setRemote(Constants.DEFAULT_REMOTE_NAME).call(); final RebaseResult rebaseResult = gitRepo.rebase().setStrategy(MergeStrategy.THEIRS) .setUpstream(upstream).setUpstreamName(upstream).call(); final Status rebaseStatus = rebaseResult.getStatus(); if (rebaseStatus.isSuccessful()) { if (!(Status.UP_TO_DATE.equals(rebaseStatus))) { hasUpdated = true; } } else { throw new WTFException(unexpectedExceptionText); } if (hasUpdated) { LOG.info("incremental plugin-update from git successful"); } else { LOG.info("plugin-files are up-to-date"); } } finally { try { if (gitRepo != null) { gitRepo.close(); } } catch (Exception closex) { LOG.debug("closing git repo failed"); } } return hasUpdated; } protected static synchronized void reloadLinkCheckerClasses() throws Exception { clearLinkCheckerCaches(); HostPluginController.getInstance().init(); try { Toolkit.setFinalPrivateField(ShutdownController.class.getDeclaredField("hooks"), ShutdownController.getInstance(), new ArrayList<ShutdownEvent>()); } catch (Exception ex) { LOG.error("could not disable unnecessary JD shutdown hooks", ex); } } protected static synchronized void clearLinkCheckerCaches() throws Exception { try { final String appPath = KrautAdminApplication.getApplicationContainingFolder(); try { FileUtils.deleteDirectory(new File(appPath.concat(File.separator.concat("cfg")))); } catch (IOException ioex) { LOG.info("Could not delete 'cfg' directory"); } try { FileUtils.deleteDirectory(new File(appPath.concat(File.separator.concat("tmp")))); } catch (IOException ioex) { LOG.info("Could not delete 'tmp' directory"); } Toolkit.setPrivateField(HostPluginController.class.getDeclaredField("lastKnownPlugins"), HostPluginController.getInstance(), new ArrayList<LazyHostPlugin>()); } catch (Exception ex) { throw new WTFException("error clearing link checker caches", ex); } //ExtensionController.getInstance().invalidateCache(); //CrawlerPluginController.invalidateCache(); HostPluginController.getInstance().invalidateCache(); } public static PluginForHost getLinkCheckerForHost(final String host) throws UpdateRequiredClassNotFoundException { final LazyHostPlugin lplugin = HostPluginController.getInstance().get(host); if (lplugin != null) { return lplugin.newInstance(PluginClassLoader.getThreadPluginClassLoaderChild()); } return null; } public static void removeCryptographyRestrictions() { if (!isRestrictedCryptography()) { LOG.info("Cryptography restrictions removal not needed"); return; } try { /* * Do the following, but with reflection to bypass access checks: * * JceSecurity.isRestricted = false; * JceSecurity.defaultPolicy.perms.clear(); * JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE); */ final Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity"); final Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions"); final Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission"); final Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted"); isRestrictedField.setAccessible(true); isRestrictedField.set(null, false); final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy"); defaultPolicyField.setAccessible(true); final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null); final Field perms = cryptoPermissions.getDeclaredField("perms"); perms.setAccessible(true); ((Map<?, ?>) perms.get(defaultPolicy)).clear(); final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE"); instance.setAccessible(true); defaultPolicy.add((Permission) instance.get(null)); LOG.info("Successfully removed cryptography restrictions"); } catch (final Exception e) { LOG.warn("Failed to remove cryptography restrictions", e); } } public static boolean isRestrictedCryptography() { // This simply matches the Oracle JRE, but not OpenJDK. return "Java(TM) SE Runtime Environment".equals(System.getProperty("java.runtime.name")); } }