All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xins.server.AccessRuleFile Maven / Gradle / Ivy

The newest version!
/*
 * $Id: AccessRuleFile.java,v 1.41 2010/09/29 17:21:48 agoubard Exp $
 *
 * See the COPYRIGHT file for redistribution and use restrictions.
 */
package org.xins.server;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.Utils;
import org.xins.common.io.FileWatcher;
import org.xins.common.text.TextUtils;
import org.xins.common.text.ParseException;

/**
 * Collection of access rules that are read from a separate file.
 *
 * 

An AccessRuleFile instance is constructed using a * descriptor and a file watch interval. The descriptor is a character string * that is parsed to determine which file should be parsed and monitored for * changes. Such a descriptor must match the following pattern: * *

file filename
* * where filename is the name of the file to parse and watch. * *

The file watch interval is specified in seconds. At the specified * interval, the file will be checked for modifications. If there are any * modifications, then the file is reloaded and the access rules are * re-applied. * *

If the file watch interval is set to 0, then the watching * is disabled, and no automatic reloading will be performed. * * @version $Revision: 1.41 $ $Date: 2010/09/29 17:21:48 $ * @author Ernst de Haan * @author Anthony Goubard * * @since XINS 1.1.0 */ public class AccessRuleFile implements AccessRuleContainer { /** * The ACL file. */ private String _file; /** * The interval used to check the ACL file for modification. */ private int _interval; /** * Watcher for the ACL file. */ private FileWatcher _fileWatcher; /** * The list of rules. Cannot be null. */ private AccessRuleContainer[] _rules; /** * String representation of this object. Cannot be null. */ private final String _asString; /** * Flag that indicates whether this object is disposed. */ private boolean _disposed; /** * Constructs a new AccessRuleFile based on a descriptor and * a file watch interval. * *

If the specified interval is 0, then no watching will be * performed. * * @param descriptor * the access rule file descriptor, the character string to parse, * cannot be null. * * @param interval * the interval to check the ACL file for modifications, in seconds, * must be >= 0. * * @throws ParseException * If the token is incorrectly formatted. * * @throws IllegalArgumentException * if descriptor == null || interval < 0. */ public AccessRuleFile(String descriptor, int interval) throws IllegalArgumentException, ParseException { // Check preconditions MandatoryArgumentChecker.check("descriptor", descriptor); if (interval < 0) { throw new IllegalArgumentException("interval (" + interval + ") < 0"); } // First token must be 'file' StringTokenizer tokenizer = new StringTokenizer(descriptor, " \t\n\r"); String token = nextToken(descriptor, tokenizer); if (! "file".equals(token)) { throw new ParseException("First token of descriptor is \"" + token + "\", instead of \"file\"."); } // First try parsing the file as it is _file = nextToken(descriptor, tokenizer); try { parseAndApply(_file, interval); // File not found } catch (FileNotFoundException fnfe) { String message = "File \"" + _file + "\" cannot be opened for reading."; ParseException pe = new ParseException(message, fnfe, null); throw pe; // I/O error reading from the file not found } catch (IOException ioe) { String message = "Cannot parse the file \"" + _file + "\" due to an I/O error."; ParseException pe = new ParseException(message, ioe, null); throw pe; } // Store the interval _interval = interval; // Create and start a file watch thread, if the interval is not zero if (interval > 0) { FileListener fileListener = new FileListener(); _fileWatcher = new FileWatcher(_file, interval, fileListener); _fileWatcher.start(); } // Generate the string representation _asString = "file " + _file; } /** * Returns the next token in the descriptor. * * @param descriptor * the original descriptor, useful when constructing the message for a * {@link ParseException}, when appropriate, should not be * null. * * @param tokenizer * the {@link StringTokenizer} to retrieve the next token from, cannot be * null. * * @return * the next token, never null. * * @throws ParseException * if tokenizer.{@link StringTokenizer#hasMoreTokens() * hasMoreTokens}() == false. */ private static String nextToken(String descriptor, StringTokenizer tokenizer) throws ParseException { if (! tokenizer.hasMoreTokens()) { String message = "The string \"" + descriptor + "\" is invalid as an access rule file descriptor. " + "More tokens expected."; throw new ParseException(message); } else { return tokenizer.nextToken(); } } /** * Determines if the specified IP address is allowed to access the * specified function, returning a Boolean object or * null. * *

This method finds the first matching rule and then returns the * allow property of that rule (see * {@link AccessRule#isAllowRule()}). If there is no matching rule, then * null is returned. * * @param ip * the IP address, cannot be null. * * @param functionName * the name of the function, cannot be null. * * @param conventionName * the name of the calling convention to match, can be null. * * @return * {@link Boolean#TRUE} if the specified IP address is allowed to access * the specified function, {@link Boolean#FALSE} if it is disallowed * access or null if there is no match. * * @throws IllegalArgumentException * if ip == null || functionName == null. * * @throws ParseException * if the specified IP address is malformed. * * @since XINS 2.1. */ public Boolean isAllowed(String ip, String functionName, String conventionName) throws IllegalArgumentException, ParseException { // Check state if (_disposed) { String detail = "This AccessRuleFile is disposed."; Utils.logProgrammingError(detail); throw new IllegalStateException(detail); } // Check arguments MandatoryArgumentChecker.check("ip", ip, "functionName", functionName); // Find a matching rule and see if the call is allowed int count = _rules == null ? 0 : _rules.length; Boolean allowed = null; for (int i = 0; i < count && allowed == null; i++) { allowed = _rules[i].isAllowed(ip, functionName, conventionName); } return allowed; } /** * Disposes this access rule. All claimed resources are freed as much as * possible. * *

Once disposed, the {@link #isAllowed} method should no longer be * called. * * @throws IllegalStateException * if {@link #dispose()} has been called previously * (since XINS 1.3.0). */ public void dispose() throws IllegalStateException { // Check state if (_disposed) { String detail = "This AccessRuleFile is already disposed."; Utils.logProgrammingError(detail); throw new IllegalStateException(detail); } // Dispose all children int count = _rules == null ? 0 : _rules.length; for (int i = 0; i < count; i++) { AccessRuleContainer rule = _rules[i]; if (rule != null) { try { rule.dispose(); } catch (Throwable exception) { Utils.logIgnoredException(exception); } } } _rules = null; // Stop the file watcher if (_fileWatcher != null) { try { _fileWatcher.end(); } catch (Throwable exception) { Utils.logIgnoredException(exception); } _fileWatcher = null; } // Mark this object as disposed _disposed = true; } /** * Reads and parses the specified ACL file and then applies it to this * AccessRuleFile instance. * * @param file * the file to open, read and parse, cannot be null. * * @param interval * the interval for checking the ACL file for modifications, in * milliseconds. * * @throws IllegalArgumentException * if file == null || interval < 0. * * @throws ParseException * if the file could not be parsed successfully. * * @throws IOException * if there was an I/O error while reading from the file. */ private void parseAndApply(String file, int interval) throws IllegalArgumentException, ParseException, IOException { // Check preconditions MandatoryArgumentChecker.check("file", file); if (interval < 0) { throw new IllegalArgumentException("interval < 0"); } // Buffer the input from the file FileReader fileReader = new FileReader(file); BufferedReader buffReader = null; try { buffReader = new BufferedReader(fileReader); // Delegate parseAndApply(file, buffReader, interval); // Always close the streams } finally { try { fileReader.close(); } catch (Throwable exception) { Utils.logIgnoredException(exception); } if (buffReader != null) { try { buffReader.close(); } catch (Throwable exception) { Utils.logIgnoredException(exception); } } } } /** * Parses the specified ACL file (already opened) and then applies it to * this AccessRuleFile instance. * * @param file * the name of the opened file, should not be null. * * @param reader * input stream for the file, should not be null. * * @param interval * the interval for checking the ACL file for modifications, in * milliseconds. * * @throws NullPointerException * if file == null || reader == null. * * @throws ParseException * if the file could not be parsed successfully. * * @throws IOException * if there was an I/O error while reading from the file. */ private void parseAndApply(String file, BufferedReader reader, int interval) throws NullPointerException, ParseException, IOException { // Loop through the file, line by line List rules = new ArrayList(25); int lineNumber = 0; String nextLine = ""; while (reader.ready() && nextLine != null) { // Read the next line and remove leading/trailing whitespace nextLine = TextUtils.trim(reader.readLine(), null); // Increase the line number (so it's 1-based) lineNumber++; // Skip comments and empty lines if (nextLine == null || nextLine.startsWith("#")) { // ignore // Plain access rule } else if (nextLine.startsWith("allow") || nextLine.startsWith("deny")) { rules.add(AccessRule.parseAccessRule(nextLine)); // File reference } else if (nextLine.startsWith("file")) { // Make sure the file does not include itself if (nextLine.substring(5).equals(file)) { String detail = "The access rule file \"" + file + "\" includes itself."; throw new ParseException(detail); } rules.add(new AccessRuleFile(nextLine, interval)); // Otherwise: Incorrect line } else { String detail = "Failed to parse \"" + file + "\", line #" + lineNumber + ": \"" + nextLine + "\". Expected line to start with \"#\", " + "\"allow\", \"deny\" or \"file\"."; throw new ParseException(detail); // XXX: Log parsing problem? } } // Copy to the instance field _rules = rules.toArray(new AccessRuleContainer[rules.size()]); } /** * Re-initializes the ACL rules for this file. */ private void reinit() { // Dispose the current rules int count = _rules == null ? 0 : _rules.length; for (int i = 0; i < count; i++) { _rules[i].dispose(); } _rules = null; // Parse the file and apply the rules try { parseAndApply(_file, _interval); // If the parsing fails, then log the exception } catch (Throwable exception) { Utils.logIgnoredException(exception); _rules = new AccessRuleContainer[0]; // TODO: The framework re-initialization should fail } } public String toString() { return _asString; } /** * Listener that reloads the ACL file if it changes. * * @version $Revision: 1.41 $ $Date: 2010/09/29 17:21:48 $ * @author Anthony Goubard * * @since XINS 1.1.0 */ private final class FileListener implements FileWatcher.Listener { /** * Constructs a new FileListener object. */ FileListener() { // empty } /** * Callback method called when the configuration file is found while it * was previously not found. * *

This will trigger re-initialization. */ public void fileFound() { reinit(); } /** * Callback method called when the configuration file is (still) not * found. * *

The implementation of this method does not perform any actions. */ public void fileNotFound() { Log.log_3400(_file); } /** * Callback method called when the configuration file is (still) not * modified. * *

The implementation of this method does not perform any actions. */ public void fileNotModified() { // empty } /** * Callback method called when the configuration file could not be * examined due to a SecurityException. * modified. * *

The implementation of this method does not perform any actions. * * @param exception * the caught security exception, should not be null * (although this is not checked). */ public void securityException(SecurityException exception) { Log.log_3401(exception, _file); } /** * Callback method called when the configuration file is modified since * the last time it was checked. * *

This will trigger re-initialization. */ public void fileModified() { reinit(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy