001 package org.bukkit.plugin.java;
002
003 import java.io.File;
004 import java.io.FileOutputStream;
005 import java.io.IOException;
006 import java.io.InputStream;
007 import java.io.OutputStream;
008 import java.net.URL;
009 import java.net.URLConnection;
010 import java.util.ArrayList;
011 import java.util.List;
012 import java.util.logging.Level;
013 import java.util.logging.Logger;
014
015 import org.bukkit.Server;
016 import org.bukkit.command.Command;
017 import org.bukkit.command.CommandSender;
018 import org.bukkit.command.PluginCommand;
019 import org.bukkit.configuration.file.FileConfiguration;
020 import org.bukkit.configuration.file.YamlConfiguration;
021 import org.bukkit.generator.ChunkGenerator;
022 import org.bukkit.plugin.PluginBase;
023 import org.bukkit.plugin.PluginDescriptionFile;
024 import org.bukkit.plugin.PluginLoader;
025 import org.bukkit.plugin.PluginLogger;
026
027 import com.avaje.ebean.EbeanServer;
028 import com.avaje.ebean.EbeanServerFactory;
029 import com.avaje.ebean.config.DataSourceConfig;
030 import com.avaje.ebean.config.ServerConfig;
031 import com.avaje.ebeaninternal.api.SpiEbeanServer;
032 import com.avaje.ebeaninternal.server.ddl.DdlGenerator;
033
034 /**
035 * Represents a Java plugin
036 */
037 public abstract class JavaPlugin extends PluginBase {
038 private boolean isEnabled = false;
039 private boolean initialized = false;
040 private PluginLoader loader = null;
041 private Server server = null;
042 private File file = null;
043 private PluginDescriptionFile description = null;
044 private File dataFolder = null;
045 private ClassLoader classLoader = null;
046 private boolean naggable = true;
047 private EbeanServer ebean = null;
048 private FileConfiguration newConfig = null;
049 private File configFile = null;
050 private PluginLogger logger = null;
051
052 public JavaPlugin() {}
053
054 /**
055 * Returns the folder that the plugin data's files are located in. The
056 * folder may not yet exist.
057 *
058 * @return The folder.
059 */
060 public final File getDataFolder() {
061 return dataFolder;
062 }
063
064 /**
065 * Gets the associated PluginLoader responsible for this plugin
066 *
067 * @return PluginLoader that controls this plugin
068 */
069 public final PluginLoader getPluginLoader() {
070 return loader;
071 }
072
073 /**
074 * Returns the Server instance currently running this plugin
075 *
076 * @return Server running this plugin
077 */
078 public final Server getServer() {
079 return server;
080 }
081
082 /**
083 * Returns a value indicating whether or not this plugin is currently enabled
084 *
085 * @return true if this plugin is enabled, otherwise false
086 */
087 public final boolean isEnabled() {
088 return isEnabled;
089 }
090
091 /**
092 * Returns the file which contains this plugin
093 *
094 * @return File containing this plugin
095 */
096 protected File getFile() {
097 return file;
098 }
099
100 /**
101 * Returns the plugin.yaml file containing the details for this plugin
102 *
103 * @return Contents of the plugin.yaml file
104 */
105 public final PluginDescriptionFile getDescription() {
106 return description;
107 }
108
109 public FileConfiguration getConfig() {
110 if (newConfig == null) {
111 reloadConfig();
112 }
113 return newConfig;
114 }
115
116 public void reloadConfig() {
117 newConfig = YamlConfiguration.loadConfiguration(configFile);
118
119 InputStream defConfigStream = getResource("config.yml");
120 if (defConfigStream != null) {
121 YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(defConfigStream);
122
123 newConfig.setDefaults(defConfig);
124 }
125 }
126
127 public void saveConfig() {
128 try {
129 getConfig().save(configFile);
130 } catch (IOException ex) {
131 logger.log(Level.SEVERE, "Could not save config to " + configFile, ex);
132 }
133 }
134
135 public void saveDefaultConfig() {
136 if (!configFile.exists()) {
137 saveResource("config.yml", false);
138 }
139 }
140
141 public void saveResource(String resourcePath, boolean replace) {
142 if (resourcePath == null || resourcePath.equals("")) {
143 throw new IllegalArgumentException("ResourcePath cannot be null or empty");
144 }
145
146 resourcePath = resourcePath.replace('\\', '/');
147 InputStream in = getResource(resourcePath);
148 if (in == null) {
149 throw new IllegalArgumentException("The embedded resource '" + resourcePath + "' cannot be found in " + file);
150 }
151
152 File outFile = new File(dataFolder, resourcePath);
153 int lastIndex = resourcePath.lastIndexOf('/');
154 File outDir = new File(dataFolder, resourcePath.substring(0, lastIndex >= 0 ? lastIndex : 0));
155
156 if (!outDir.exists()) {
157 outDir.mkdirs();
158 }
159
160 try {
161 if (!outFile.exists() || replace) {
162 OutputStream out = new FileOutputStream(outFile);
163 byte[] buf = new byte[1024];
164 int len;
165 while ((len = in.read(buf)) > 0) {
166 out.write(buf, 0, len);
167 }
168 out.close();
169 in.close();
170 } else {
171 logger.log(Level.WARNING, "Could not save " + outFile.getName() + " to " + outFile + " because " + outFile.getName() + " already exists.");
172 }
173 } catch (IOException ex) {
174 logger.log(Level.SEVERE, "Could not save " + outFile.getName() + " to " + outFile, ex);
175 }
176 }
177
178 public InputStream getResource(String filename) {
179 if (filename == null) {
180 throw new IllegalArgumentException("Filename cannot be null");
181 }
182
183 try {
184 URL url = getClassLoader().getResource(filename);
185
186 if (url == null) {
187 return null;
188 }
189
190 URLConnection connection = url.openConnection();
191 connection.setUseCaches(false);
192 return connection.getInputStream();
193 } catch (IOException ex) {
194 return null;
195 }
196 }
197
198 /**
199 * Returns the ClassLoader which holds this plugin
200 *
201 * @return ClassLoader holding this plugin
202 */
203 protected final ClassLoader getClassLoader() {
204 return classLoader;
205 }
206
207 /**
208 * Sets the enabled state of this plugin
209 *
210 * @param enabled true if enabled, otherwise false
211 */
212 protected final void setEnabled(final boolean enabled) {
213 if (isEnabled != enabled) {
214 isEnabled = enabled;
215
216 if (isEnabled) {
217 onEnable();
218 } else {
219 onDisable();
220 }
221 }
222 }
223
224 /**
225 * Initializes this plugin with the given variables.
226 * <p />
227 * This method should never be called manually.
228 *
229 * @param loader PluginLoader that is responsible for this plugin
230 * @param server Server instance that is running this plugin
231 * @param description PluginDescriptionFile containing metadata on this plugin
232 * @param dataFolder Folder containing the plugin's data
233 * @param file File containing this plugin
234 * @param classLoader ClassLoader which holds this plugin
235 */
236 protected final void initialize(PluginLoader loader, Server server, PluginDescriptionFile description, File dataFolder, File file, ClassLoader classLoader) {
237 if (!initialized) {
238 this.initialized = true;
239 this.loader = loader;
240 this.server = server;
241 this.file = file;
242 this.description = description;
243 this.dataFolder = dataFolder;
244 this.classLoader = classLoader;
245 this.configFile = new File(dataFolder, "config.yml");
246 this.logger = new PluginLogger(this);
247
248 if (description.isDatabaseEnabled()) {
249 ServerConfig db = new ServerConfig();
250
251 db.setDefaultServer(false);
252 db.setRegister(false);
253 db.setClasses(getDatabaseClasses());
254 db.setName(description.getName());
255 server.configureDbConfig(db);
256
257 DataSourceConfig ds = db.getDataSourceConfig();
258
259 ds.setUrl(replaceDatabaseString(ds.getUrl()));
260 dataFolder.mkdirs();
261
262 ClassLoader previous = Thread.currentThread().getContextClassLoader();
263
264 Thread.currentThread().setContextClassLoader(classLoader);
265 ebean = EbeanServerFactory.create(db);
266 Thread.currentThread().setContextClassLoader(previous);
267 }
268 }
269 }
270
271 /**
272 * Provides a list of all classes that should be persisted in the database
273 *
274 * @return List of Classes that are Ebeans
275 */
276 public List<Class<?>> getDatabaseClasses() {
277 return new ArrayList<Class<?>>();
278 }
279
280 private String replaceDatabaseString(String input) {
281 input = input.replaceAll("\\{DIR\\}", dataFolder.getPath().replaceAll("\\\\", "/") + "/");
282 input = input.replaceAll("\\{NAME\\}", description.getName().replaceAll("[^\\w_-]", ""));
283 return input;
284 }
285
286 /**
287 * Gets the initialization status of this plugin
288 *
289 * @return true if this plugin is initialized, otherwise false
290 */
291 public final boolean isInitialized() {
292 return initialized;
293 }
294
295 /**
296 * {@inheritDoc}
297 */
298 public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
299 return false;
300 }
301
302 /**
303 * {@inheritDoc}
304 */
305 public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
306 return null;
307 }
308
309 /**
310 * Gets the command with the given name, specific to this plugin
311 *
312 * @param name Name or alias of the command
313 * @return PluginCommand if found, otherwise null
314 */
315 public PluginCommand getCommand(String name) {
316 String alias = name.toLowerCase();
317 PluginCommand command = getServer().getPluginCommand(alias);
318
319 if ((command != null) && (command.getPlugin() != this)) {
320 command = getServer().getPluginCommand(description.getName().toLowerCase() + ":" + alias);
321 }
322
323 if ((command != null) && (command.getPlugin() == this)) {
324 return command;
325 } else {
326 return null;
327 }
328 }
329
330 public void onLoad() {}
331
332 public void onDisable() {}
333
334 public void onEnable() {}
335
336 public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
337 getServer().getLogger().severe("Plugin " + description.getFullName() + " does not contain any generators that may be used in the default world!");
338 return null;
339 }
340
341 public final boolean isNaggable() {
342 return naggable;
343 }
344
345 public final void setNaggable(boolean canNag) {
346 this.naggable = canNag;
347 }
348
349 public EbeanServer getDatabase() {
350 return ebean;
351 }
352
353 protected void installDDL() {
354 SpiEbeanServer serv = (SpiEbeanServer) getDatabase();
355 DdlGenerator gen = serv.getDdlGenerator();
356
357 gen.runScript(false, gen.generateCreateDdl());
358 }
359
360 protected void removeDDL() {
361 SpiEbeanServer serv = (SpiEbeanServer) getDatabase();
362 DdlGenerator gen = serv.getDdlGenerator();
363
364 gen.runScript(true, gen.generateDropDdl());
365 }
366
367 public final Logger getLogger() {
368 return logger;
369 }
370
371 @Override
372 public String toString() {
373 return description.getFullName();
374 }
375 }