View Javadoc

1   /*******************************************************************************
2    * Copyright (c) 2007, 2014 Massimiliano Ziccardi
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *******************************************************************************/
16  package it.jnrpe.utils;
17  
18  import it.jnrpe.plugins.IPluginInterface;
19  import it.jnrpe.plugins.PluginConfigurationException;
20  import it.jnrpe.plugins.PluginDefinition;
21  import it.jnrpe.plugins.PluginOption;
22  import it.jnrpe.plugins.PluginRepository;
23  import it.jnrpe.plugins.annotations.Option;
24  import it.jnrpe.plugins.annotations.Plugin;
25  import it.jnrpe.plugins.annotations.PluginOptions;
26  
27  import java.io.InputStream;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Iterator;
31  import java.util.List;
32  
33  import javax.xml.parsers.DocumentBuilder;
34  import javax.xml.parsers.DocumentBuilderFactory;
35  
36  import org.apache.commons.lang.StringUtils;
37  import org.dom4j.Document;
38  import org.dom4j.Element;
39  import org.dom4j.io.DOMReader;
40  
41  /**
42   * An utility class that allows to define the plugin repository in an XML file
43   * instead that using Java Code.
44   * 
45   * @author Massimiliano Ziccardi
46   */
47  public final class PluginRepositoryUtil {
48  
49  	/**
50       *
51       */
52  	private PluginRepositoryUtil() {
53  
54  	}
55  
56  	/**
57  	 * Loads a full repository definition from an XML file.
58  	 * 
59  	 * @param repo
60  	 *            The repository that must be loaded
61  	 * @param cl
62  	 *            The classloader to be used to instantiate the plugin classes
63  	 * @param in
64  	 *            The stream to the XML file
65  	 * @throws PluginConfigurationException
66  	 *             -
67  	 */
68  	public static void loadFromXmlPluginPackageDefinitions(
69  			final PluginRepository repo, final ClassLoader cl,
70  			final InputStream in) throws PluginConfigurationException {
71  		for (PluginDefinition pd : loadFromXmlPluginPackageDefinitions(cl, in)) {
72  			repo.addPluginDefinition(pd);
73  		}
74  	}
75  
76  	/**
77  	 * Loads the plugins definitions from the jnrpe_plugins.xml file.
78  	 * 
79  	 * @param cl
80  	 *            Classloader to be used to load classes
81  	 * @param in
82  	 *            InputStream to the jnrpe_plugins.xml file
83  	 * @return a collection of all the declared plugins
84  	 * @throws PluginConfigurationException
85  	 *             on any error reading the plugin configuration
86  	 */
87  	@SuppressWarnings("unchecked")
88  	public static Collection<PluginDefinition> loadFromXmlPluginPackageDefinitions(
89  			final ClassLoader cl, final InputStream in)
90  			throws PluginConfigurationException {
91  
92  		List<PluginDefinition> res = new ArrayList<PluginDefinition>();
93  
94  		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
95  
96  		Document document;
97  
98  		try {
99  			DocumentBuilder loader = factory.newDocumentBuilder();
100 			DOMReader reader = new DOMReader();
101 
102 			document = reader.read(loader.parse(in));
103 		} catch (Exception e) {
104 			throw new PluginConfigurationException(e);
105 		}
106 
107 		Element plugins = document.getRootElement();
108 
109 		// TODO : validate against schema
110 
111 		// iterate through child elements of root
112 		for (Iterator<Element> i = plugins.elementIterator(); i.hasNext();) {
113 			Element plugin = i.next();
114 
115 			PluginDefinition pd = parsePluginDefinition(cl, plugin);
116 			res.add(pd);
117 		}
118 
119 		return res;
120 	}
121 
122 	/**
123 	 * Loads the definition of a single plugin from an XML file.
124 	 * 
125 	 * @param cl
126 	 *            The classloader to be used to instantiate the plugin class
127 	 * @param in
128 	 *            The stream to the XML file
129 	 * @return The plugin definition
130 	 * @throws PluginConfigurationException
131 	 *             -
132 	 */
133 	public static PluginDefinition parseXmlPluginDefinition(
134 			final ClassLoader cl, final InputStream in)
135 			throws PluginConfigurationException {
136 
137 		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
138 
139 		Document document;
140 
141 		try {
142 			DocumentBuilder loader = factory.newDocumentBuilder();
143 			DOMReader reader = new DOMReader();
144 
145 			document = reader.read(loader.parse(in));
146 		} catch (Exception e) {
147 			throw new PluginConfigurationException(e);
148 		}
149 
150 		Element plugin = document.getRootElement();
151 
152 		// TODO : validate against schema
153 
154 		return parsePluginDefinition(cl, plugin);
155 	}
156 
157 	/**
158 	 * Returns the value of the given attribute name of element. If mandatory is
159 	 * <code>true</code> and the attribute is blank or null, an exception is
160 	 * thrown.
161 	 * 
162 	 * @param element
163 	 *            The element whose attribute must be read
164 	 * @param attributeName
165 	 *            The attribute name
166 	 * @param mandatory
167 	 *            <code>true</code> if the attribute is mandatory
168 	 * @return the attribute value
169 	 * @throws PluginConfigurationException
170 	 *             if the attribute can't be found or is blank
171 	 */
172 	private static String getAttributeValue(final Element element,
173 			final String attributeName, final boolean mandatory)
174 			throws PluginConfigurationException {
175 		String returnValue = element.attributeValue(attributeName);
176 
177 		if (mandatory) {
178 			if (StringUtils.isBlank(returnValue)) {
179 				throw new PluginConfigurationException(
180 						"Error loading plugin package : mandatory attribute "
181 								+ attributeName + " not found");
182 
183 			}
184 		}
185 
186 		return returnValue;
187 	}
188 
189 	/**
190 	 * Parse an XML plugin definition.
191 	 * 
192 	 * @param cl
193 	 *            The classloader to be used to load classes
194 	 * @param plugin
195 	 *            The plugin XML element
196 	 * @return the parsed plugin definition
197 	 * @throws PluginConfigurationException
198 	 *             -
199 	 */
200 	@SuppressWarnings("rawtypes")
201 	private static PluginDefinition parsePluginDefinition(final ClassLoader cl,
202 			final Element plugin) throws PluginConfigurationException {
203 
204 		// Check if the plugin definition is inside its own file
205 		if (getAttributeValue(plugin, "definedIn", false) != null) {
206 			StreamManager sm = new StreamManager();
207 
208 			String sFileName = getAttributeValue(plugin, "definedIn", false);
209 
210 			try {
211 				InputStream in = sm.handle(cl.getResourceAsStream(sFileName));
212 
213 				return parseXmlPluginDefinition(cl, in);
214 			} finally {
215 				sm.closeAll();
216 			}
217 		}
218 
219 		String pluginClass = getAttributeValue(plugin, "class", true);
220 
221 		Class c;
222 		try {
223 			c = LoadedClassCache.getClass(cl, pluginClass);
224 
225 			if (!IPluginInterface.class.isAssignableFrom(c)) {
226 				throw new PluginConfigurationException("Specified class '"
227 						+ c.getName()
228 						+ "' in the plugin.xml file does not implement "
229 						+ "the IPluginInterface interface");
230 			}
231 
232 			if (isAnnotated(c)) {
233 				return loadFromPluginAnnotation(c);
234 			}
235 
236 		} catch (ClassNotFoundException e) {
237 			throw new PluginConfigurationException(e);
238 		}
239 
240 		// The class is not annotated not has an external definition file...
241 		// Loading from current xml file...
242 
243 		String sDescription = getAttributeValue(plugin, "description", false);
244 
245 		@SuppressWarnings("unchecked")
246 		PluginDefinition pluginDef = new PluginDefinition(getAttributeValue(
247 				plugin, "name", true), sDescription, c);
248 
249 		parseCommandLine(pluginDef, plugin);
250 		return pluginDef;
251 	}
252 
253 	/**
254 	 * Updates the plugin definition with the commandline read from the xml
255 	 * file.
256 	 * 
257 	 * @param pluginDef
258 	 *            The plugin definition to be updated
259 	 * @param xmlPluginElement
260 	 *            the xml element to be parsed
261 	 */
262 	@SuppressWarnings("rawtypes")
263 	private static void parseCommandLine(final PluginDefinition pluginDef,
264 			final Element xmlPluginElement) {
265 		Element commandLine = xmlPluginElement.element("command-line");
266 
267 		if (commandLine != null) {
268 			// The plugin has a command line...
269 			Element options = commandLine.element("options");
270 
271 			if (options == null) {
272 				// The command line is empty...
273 				return;
274 			}
275 
276 			for (Iterator i = options.elementIterator(); i.hasNext();) {
277 				Element option = (Element) i.next();
278 
279 				PluginOption po = parsePluginOption(option);
280 
281 				pluginDef.addOption(po);
282 			}
283 		}
284 	}
285 
286 	/**
287 	 * Returns <code>true</code> if the class contains plugin annotations.
288 	 * 
289 	 * @param clazz
290 	 *            The plugin class
291 	 * @return <code>true</code> if the class contains plugin
292 	 */
293 	@SuppressWarnings({ "rawtypes", "unchecked" })
294 	private static boolean isAnnotated(final Class clazz) {
295 		Plugin plugin = (Plugin) clazz.getAnnotation(Plugin.class);
296 		return plugin != null;
297 	}
298 
299 	/**
300 	 * Parse a plugin from class annotations.
301 	 * 
302 	 * @param clazz
303 	 *            the plugin class
304 	 * @return PluginDefinition
305 	 */
306 	@SuppressWarnings({ "rawtypes", "unchecked" })
307 	public static PluginDefinition loadFromPluginAnnotation(final Class clazz) {
308 		Plugin plugin = (Plugin) clazz.getAnnotation(Plugin.class);
309 		PluginOptions options = (PluginOptions) clazz
310 				.getAnnotation(PluginOptions.class);
311 		String name = plugin.name();
312 		String description = plugin.description();
313 		PluginDefinition def = new PluginDefinition(name, description, clazz);
314 		for (Option option : options.value()) {
315 			def.addOption(parsePluginOption(option));
316 		}
317 		return def;
318 	}
319 
320 	/**
321 	 * Parses a plugin option XML definition.
322 	 * 
323 	 * @param option
324 	 *            The plugin option XML definition
325 	 * @return The parsed plugin option
326 	 */
327 	private static PluginOption parsePluginOption(final Element option) {
328 		PluginOption po = new PluginOption();
329 		po.setArgName(option.attributeValue("argName"));
330 		po.setArgsCount(Integer.parseInt(option
331 				.attributeValue("argsCount", "1")));
332 		po.setArgsOptional(Boolean.valueOf(option.attributeValue(
333 				"optionalArgs", "false")));
334 		po.setDescription(option.attributeValue("description"));
335 		po.setHasArgs(Boolean.valueOf(option.attributeValue("hasArgs", "false")));
336 		po.setLongOpt(option.attributeValue("longName"));
337 		po.setOption(option.attributeValue("shortName"));
338 		po.setRequired(Boolean.valueOf(option.attributeValue("required",
339 				"false")));
340 		po.setType(option.attributeValue("type"));
341 		po.setValueSeparator(option.attributeValue("separator"));
342 
343 		return po;
344 	}
345 
346 	/**
347 	 * Parses a plugin option from the annotation definition.
348 	 * 
349 	 * @param option
350 	 *            the plugin option
351 	 * @return PluginOption
352 	 */
353 	private static PluginOption parsePluginOption(final Option option) {
354 		PluginOption po = new PluginOption();
355 		po.setArgName(option.argName());
356 		po.setArgsOptional(option.optionalArgs());
357 		po.setDescription(option.description());
358 		po.setHasArgs(option.hasArgs());
359 		po.setLongOpt(option.longName());
360 		po.setOption(option.shortName());
361 		po.setRequired(option.required());
362 		return po;
363 
364 	}
365 }