/* * ShootOFF - Software for Laser Dry Fire Training * Copyright (C) 2016 phrack * * 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 com.shootoff.plugins.engine; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; import java.security.AccessController; import java.security.PrivilegedAction; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import com.shootoff.plugins.ProjectorTrainingExerciseBase; import com.shootoff.plugins.TrainingExercise; import com.shootoff.plugins.TrainingExerciseBase; public class Plugin { private static final Logger logger = LoggerFactory.getLogger(Plugin.class); private final URLClassLoader loader; private final TrainingExercise exercise; private final Path jarPath; private final PluginType type; public Plugin(final Path jarPath) throws ParserConfigurationException, SAXException, IOException { this.jarPath = jarPath; loader = AccessController.doPrivileged((PrivilegedAction<URLClassLoader>) () -> { try { return new URLClassLoader(new URL[] { jarPath.toUri().toURL() }, Thread.currentThread().getContextClassLoader()); } catch (final MalformedURLException e) { logger.error("Malformed jarPath", e); } return null; }); if (loader == null) { throw new IllegalArgumentException( String.format("The jarPath %s does not represent a valid ShootOFF plugin", jarPath)); } final InputStream pluginSettings = loader.getResourceAsStream("shootoff.xml"); if (pluginSettings == null) { throw new IllegalArgumentException( String.format("The jarPath %s does not represent a valid ShootOFF plugin", jarPath)); } final SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser(); final PluginSettingsXMLHandler handler = new PluginSettingsXMLHandler(); saxParser.parse(pluginSettings, handler); if (handler.getExercise() == null) { throw new IllegalArgumentException( String.format("Could not fetch main class for newly discovered exercise at %s", jarPath)); } exercise = handler.getExercise(); type = handler.getType(); } private class PluginSettingsXMLHandler extends DefaultHandler { private TrainingExercise exercise; private PluginType type; public TrainingExercise getExercise() { return exercise; } public PluginType getType() { return type; } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { switch (qName) { case "shootoffExercise": { final String mainClassName = attributes.getValue("exerciseClass"); final Class<?> exerciseClass; try { exerciseClass = loader.loadClass(mainClassName); } catch (final ClassNotFoundException e) { logger.error("Configured exerciseClass ({}) was not found", mainClassName); return; } // No superclass or not a known training exercise superclass final boolean isStandardExercise = exerciseClass.getSuperclass().getName() .equals(TrainingExerciseBase.class.getName()); final boolean isProjectorOnlyExercise = exerciseClass.getSuperclass().getName() .equals(ProjectorTrainingExerciseBase.class.getName()); if (exerciseClass.getSuperclass() == null || (!isStandardExercise && !isProjectorOnlyExercise)) { final String superclassName; if (exerciseClass.getSuperclass() == null) { superclassName = "null"; } else { superclassName = exerciseClass.getSuperclass().getName(); } logger.error( "Configured exerciseClass ({}) does not have a known training exercise superclass, type is {}", mainClassName, superclassName); return; } if (isStandardExercise) { type = PluginType.STANDARD; } else if (isProjectorOnlyExercise) { type = PluginType.PROJECTOR_ONLY; } try { exercise = (TrainingExercise) exerciseClass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { logger.error("Error instantiating configured exerciseClass", e); } } break; default: logger.warn("Unrecognized exercise settings tag ignored: {}", qName); } } } public URLClassLoader getLoader() { return loader; } public TrainingExercise getExercise() { return exercise; } public Path getJarPath() { return jarPath; } public PluginType getType() { return type; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((jarPath == null) ? 0 : jarPath.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final Plugin other = (Plugin) obj; if (jarPath == null) { if (other.jarPath != null) return false; } else if (!jarPath.equals(other.jarPath)) return false; return true; } }