/* * Copyright 2011 Tyler Blair. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and contributors and should not be interpreted as representing official policies, * either expressed or implied, of anybody else. */ package com.griefcraft.model; import com.griefcraft.cache.ProtectionCache; import com.griefcraft.lwc.LWC; import com.griefcraft.scripting.event.LWCProtectionRemovePostEvent; import com.griefcraft.util.Colors; import com.griefcraft.util.ProtectionFinder; import com.griefcraft.util.StringUtil; import com.griefcraft.util.TimeUtil; import com.griefcraft.util.UUIDRegistry; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; public class Protection { /** * The protection type * * <p>Ordering <b>must NOT change</b> as ordinal values are used</p> */ public enum Type { /** * The protection is usable by anyone; the most common use would include community chests * where anyone can use the chest but no one should be able to protect as their own. */ PUBLIC, /** * The owner (and anyone else) must enter a set password entered onto the chest in order * to be able to access it. Entering the correct password allows them to use the chest * until they log out or the protection is removed. */ PASSWORD, /** * The protection is only usable by the player who created it. Further access can be * given to players, groups, and even more specific entities * such as Towns in the "Towny" plugin, or access lists via the "Lists" plugin */ PRIVATE, /** * Reserved / unused, to keep ordinal order */ RESERVED1, /** * Reserved / unused, to keep ordinal order */ RESERVED2, /** * Allows players to deposit items into */ DONATION; /** * Match a protection type using its string form * * @param text * @return */ public static Type matchType(String text) { for (Type type : values()) { if (type.toString().equalsIgnoreCase(text)) { return type; } } throw new IllegalArgumentException("No Protection Type found for given type: " + text); } } /** * All of the history items associated with this protection */ private final Set<History> historyCache = new HashSet<History>(); /** * List of the permissions rights for the protection */ private final Set<Permission> permissions = new HashSet<Permission>(); /** * List of flags enabled on the protection */ private final Map<Flag.Type, Flag> flags = new HashMap<Flag.Type, Flag>(); /** * The block id */ private int blockId; /** * The password for the chest */ private String password; /** * JSON data for the protection */ private final JSONObject data = new JSONObject(); /** * Unique id (in sql) */ private int id; /** * The owner of the chest */ private String owner; /** * The protection type */ private Type type; /** * The world the protection is in */ private String world; /** * The x coordinate */ private int x; /** * The y coordinate */ private int y; /** * The z coordinate */ private int z; /** * The timestamp of when the protection was last accessed */ private long lastAccessed; /** * The time the protection was created */ private String creation; /** * Immutable flag for the protection. When removed, this bool is switched to true and any setters * will no longer work. However, everything is still intact and in memory at this point (for now.) */ private boolean removed = false; /** * If the protection is pending removal. Only used internally. */ private boolean removing = false; /** * True when the protection has been modified and should be saved */ private boolean modified = false; /** * The protection finder used to find this protection */ private ProtectionFinder finder; /** * The block the protection is at. Saves world calls and allows better concurrency */ private Block cachedBlock; @Override public boolean equals(Object object) { if (!(object instanceof Protection)) { return false; } Protection other = (Protection) object; return id == other.id && x == other.x && y == other.y && z == other.z && (owner != null && owner.equals(other.owner)) && (world != null && world.equals(other.world)); } @Override public int hashCode() { int hash = 17; // the identifier is normally unique, but in SQLite ids may be quickly reused so we use other data hash *= 37 + id; // coordinates hash *= 37 + x; hash *= 37 + y; hash *= 37 + z; // and for good measure, to *guarantee* no collisions if (creation != null) { hash *= 37 + creation.hashCode(); } return hash; } /** * Convert the protection to use UUIDs * * @return true if the protection required conversion and conversions were done */ public boolean convertPlayerNamesToUUIDs() { if (!needsUUIDConversion()) { return false; } boolean res = false; if (!UUIDRegistry.isValidUUID(owner)) { UUID uuid = UUIDRegistry.getUUID(owner); if (uuid != null) { setOwner(uuid.toString()); res = true; } } for (Permission permission : permissions) { if (permission.getType() == Permission.Type.PLAYER && !UUIDRegistry.isValidUUID(permission.getName())) { UUID uuid = UUIDRegistry.getUUID(permission.getName()); if (uuid != null) { permission.setName(uuid.toString()); modified = true; res = true; } } } return res; } /** * Check if this protection requires conversion from plain player names to UUIDs * * @return true if the protection requires conversion */ public boolean needsUUIDConversion() { if (!UUIDRegistry.isValidUUID(owner)) { return true; } for (Permission permission : permissions) { if (permission.getType() == Permission.Type.PLAYER && !UUIDRegistry.isValidUUID(permission.getName())) { return true; } } return false; } /** * Get a formatted version of the owner's name. If the owner is a UUID and the UUID is unknown, then * "Unknown (uuid)" will be returned. * * @return */ public String getFormattedOwnerPlayerName() { return UUIDRegistry.formatPlayerName(owner); } /** * Encode the AccessRights to JSON * * @return */ public void encodeRights() { // create the root JSONArray root = new JSONArray(); // add all of the permissions to the root for (Permission permission : permissions) { if (permission != null) { root.add(permission.encodeToJSON()); } } data.put("rights", root); } /** * Encode the protection flags to JSON */ public void encodeFlags() { JSONArray root = new JSONArray(); for (Flag flag : flags.values()) { if (flag != null) { root.add(flag.getData()); } } data.put("flags", root); } /** * Ensure a history object is located in our cache * * @param history */ public void checkHistory(History history) { if (!historyCache.contains(history)) { historyCache.add(history); } } /** * Check if a player has owner access to the protection * * @param player * @return */ public boolean isOwner(Player player) { LWC lwc = LWC.getInstance(); if (isRealOwner(player)) { return true; } else { return lwc.isAdmin(player); } } /** * Check if a player is the real owner to the protection * * @param player * @return */ public boolean isRealOwner(Player player) { if (player == null) { return false; } if (UUIDRegistry.isValidUUID(owner)) { return UUID.fromString(owner).equals(player.getUniqueId()); } else { return owner.equalsIgnoreCase(player.getName()); } } /** * Create a History object that is attached to this protection * * @return */ public History createHistoryObject() { History history = new History(); history.setProtectionId(id); history.setProtection(this); history.setStatus(History.Status.INACTIVE); history.setX(x); history.setY(y); history.setZ(z); // add it to the cache historyCache.add(history); return history; } /** * @return the related history for this protection, which is immutable */ public Set<History> getRelatedHistory() { // cache the database's history if we don't have any yet if (historyCache.size() == 0) { historyCache.addAll(LWC.getInstance().getPhysicalDatabase().loadHistory(this)); } // now we can return an immutable cache return Collections.unmodifiableSet(historyCache); } /** * Get the related history for this protection using the given type * * @param type * @return */ public List<History> getRelatedHistory(History.Type type) { List<History> matches = new ArrayList<History>(); Set<History> relatedHistory = getRelatedHistory(); for (History history : relatedHistory) { if (history.getType() == type) { matches.add(history); } } return matches; } /** * Check if a flag is enabled * * @param type * @return */ public boolean hasFlag(Flag.Type type) { return flags.containsKey(type); } /** * Get the enabled flag for the corresponding type * * @param type * @return */ public Flag getFlag(Flag.Type type) { return flags.get(type); } /** * Add a flag to the protection * * @param flag * @return */ public boolean addFlag(Flag flag) { if (removed || flag == null) { return false; } if (!flags.containsKey(flag.getType())) { flags.put(flag.getType(), flag); modified = true; return true; } return false; } /** * Remove a flag from the protection * * @param flag * @return */ public void removeFlag(Flag flag) { if (removed) { return; } flags.remove(flag.getType()); this.modified = true; } /** * Check if the entity + permissions type exists, and if so return the rights (-1 if it does not exist) * * @param type * @param name * @return the permissions the player has */ public Permission.Access getAccess(String name, Permission.Type type) { for (Permission permission : permissions) { if (permission.getType() == type && permission.getName().equalsIgnoreCase(name)) { return permission.getAccess(); } } return Permission.Access.NONE; } /** * @return the list of permissions */ public List<Permission> getPermissions() { return Collections.unmodifiableList(new ArrayList<Permission>(permissions)); } /** * Remove temporary permissions rights from the protection */ public void removeTemporaryPermissions() { Iterator<Permission> iter = permissions.iterator(); while (iter.hasNext()) { Permission permission = iter.next(); if (permission.isVolatile()) { iter.remove(); } } } /** * Add an permission to the protection * * @param permission */ public void addPermission(Permission permission) { if (removed || permission == null) { return; } // remove any other rights with the same identity removePermissions(permission.getName(), permission.getType()); // now we can safely add it permissions.add(permission); modified = true; } /** * Remove permissions from the protection that match a name AND type * * @param name * @param type */ public void removePermissions(String name, Permission.Type type) { if (removed) { return; } Iterator<Permission> iter = permissions.iterator(); while (iter.hasNext()) { Permission permission = iter.next(); if ((permission.getName().equals(name) || name.equals("*")) && permission.getType() == type) { iter.remove(); modified = true; } } } /** * Remove all of the permissions */ public void removeAllPermissions() { permissions.clear(); modified = true; } /** * Checks if the protection has the correct block in the world * * @return */ public boolean isBlockInWorld() { int storedBlockId = getBlockId(); Block block = getBlock(); switch (block.getType()) { case FURNACE: case BURNING_FURNACE: return storedBlockId == Material.FURNACE.getId() || storedBlockId == Material.BURNING_FURNACE.getId(); case STEP: case DOUBLE_STEP: return storedBlockId == Material.STEP.getId() || storedBlockId == Material.DOUBLE_STEP.getId(); default: return storedBlockId == block.getTypeId(); } } public JSONObject getData() { return data; } public int getBlockId() { return blockId; } public String getPassword() { return password; } public String getCreation() { return creation; } public int getId() { return id; } public String getOwner() { return owner; } public Type getType() { return type; } public String getWorld() { return world; } public int getX() { return x; } public int getY() { return y; } public int getZ() { return z; } public long getLastAccessed() { return lastAccessed; } public void setBlockId(int blockId) { if (removed) { return; } this.blockId = blockId; this.modified = true; } public void setPassword(String password) { if (removed) { return; } this.password = password; this.modified = true; } public void setCreation(String creation) { if (removed) { return; } this.creation = creation; this.modified = true; } public void setId(int id) { if (removed) { return; } this.id = id; this.modified = true; } public void setOwner(String owner) { if (removed) { return; } this.owner = owner; this.modified = true; } public void setType(Type type) { if (removed) { return; } this.type = type; this.modified = true; } public void setWorld(String world) { if (removed) { return; } this.world = world; this.modified = true; } public void setX(int x) { if (removed) { return; } this.x = x; this.modified = true; } public void setY(int y) { if (removed) { return; } this.y = y; this.modified = true; } public void setZ(int z) { if (removed) { return; } this.z = z; this.modified = true; } public void setLastAccessed(long lastAccessed) { if (removed) { return; } this.lastAccessed = lastAccessed; this.modified = true; } /** * Sets the protection finder used to create this protection * * @param finder */ public void setProtectionFinder(ProtectionFinder finder) { this.finder = finder; } /** * Gets the protection finder used the create this protection * * @return the ProtectionFinder used to create this protection */ public ProtectionFinder getProtectionFinder() { return finder; } /** * Remove the protection from the database */ public void remove() { if (removed) { return; } LWC lwc = LWC.getInstance(); removeTemporaryPermissions(); // we're removing it, so assume there are no changes modified = false; removing = true; // broadcast the removal event // we broadcast before actually removing to give them a chance to use any password that would be removed otherwise lwc.getModuleLoader().dispatchEvent(new LWCProtectionRemovePostEvent(this)); // mark related transactions as inactive for (History history : getRelatedHistory(History.Type.TRANSACTION)) { if (history.getStatus() != History.Status.ACTIVE) { continue; } history.setStatus(History.Status.INACTIVE); } // ensure all history objects for this protection are saved checkAndSaveHistory(); // make the protection immutable removed = true; // and now finally remove it from the database lwc.getDatabaseThread().removeProtection(this); lwc.getPhysicalDatabase().removeProtection(id); removeCache(); } /** * Remove the protection from cache */ public void removeCache() { LWC lwc = LWC.getInstance(); lwc.getProtectionCache().removeProtection(this); radiusRemoveCache(); } /** * Remove blocks around the protection in a radius of 3, to account for broken known / null blocks */ public void radiusRemoveCache() { ProtectionCache cache = LWC.getInstance().getProtectionCache(); for (int x = -3; x <= 3; x++) { for (int y = -3; y <= 3; y++) { for (int z = -3; z <= 3; z++) { String cacheKey = world + ":" + (this.x + x) + ":" + (this.y + y) + ":" + (this.z + z); // get the protection for that entry Protection protection = cache.getProtection(cacheKey); // the ifnull compensates for the block being in the null cache. It will remove it from that. if ((protection != null && id == protection.getId()) || protection == null) { cache.remove(cacheKey); } } } } } /** * Queue the protection to be saved */ public void save() { if (removed) { return; } LWC.getInstance().getDatabaseThread().addProtection(this); } /** * Force a protection update to the live database */ public void saveNow() { if (removed) { return; } // encode JSON objects encodeRights(); encodeFlags(); // only save the protection if it was modified if (modified && !removing) { LWC.getInstance().getPhysicalDatabase().saveProtection(this); } // check the cache for history updates checkAndSaveHistory(); } /** * Saves any of the history items for the Protection that have been modified */ public void checkAndSaveHistory() { if (removed) { return; } for (History history : getRelatedHistory()) { // if the history object was modified we need to save it if (history.wasModified()) { history.saveNow(); } } } /** * @return the key used for the protection cache */ public String getCacheKey() { return world + ":" + x + ":" + y + ":" + z; } /** * @return the Bukkit world the protection should be located in */ public World getBukkitWorld() { if (world == null || world.isEmpty()) { return Bukkit.getServer().getWorlds().get(0); } return Bukkit.getServer().getWorld(world); } /** * @return the Bukkit Player object of the owner */ public Player getBukkitOwner() { return Bukkit.getServer().getPlayer(owner); } /** * @return the block representing the protection in the world */ public Block getBlock() { if (cachedBlock != null) { return cachedBlock; } World world = getBukkitWorld(); if (world == null) { return null; } cachedBlock = world.getBlockAt(x, y, z); return cachedBlock; } /** * @return */ @Override public String toString() { // format the flags prettily String flagStr = ""; for (Flag flag : flags.values()) { flagStr += flag.toString() + ","; } if (flagStr.endsWith(",")) { flagStr = flagStr.substring(0, flagStr.length() - 1); } // format the last accessed time String lastAccessed = TimeUtil.timeToString((System.currentTimeMillis() / 1000L) - this.lastAccessed); if (!lastAccessed.equals("Not yet known")) { lastAccessed += " ago"; } return String.format("%s %s" + Colors.White + " " + Colors.Green + "Id=%d Owner=%s Location=[%s %d,%d,%d] Created=%s Flags=%s LastAccessed=%s", typeToString(), (blockId > 0 ? (LWC.materialToString(blockId)) : "Not yet cached"), id, owner, world, x, y, z, creation, flagStr, lastAccessed); } /** * @return string representation of the protection type */ public String typeToString() { return StringUtil.capitalizeFirstLetter(type.toString()); } /** * Updates the protection in the protection cache */ @Deprecated public void update() { throw new UnsupportedOperationException("Protection.update() is no longer necessary!"); } }