Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ package org.apache.openaz.xacml.std.pip.engines.csv; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.openaz.xacml.api.Attribute; import org.apache.openaz.xacml.api.AttributeValue; import org.apache.openaz.xacml.api.DataType; import org.apache.openaz.xacml.api.DataTypeException; import org.apache.openaz.xacml.api.DataTypeFactory; import org.apache.openaz.xacml.api.pip.PIPException; import org.apache.openaz.xacml.api.pip.PIPFinder; import org.apache.openaz.xacml.api.pip.PIPRequest; import org.apache.openaz.xacml.api.pip.PIPResponse; import org.apache.openaz.xacml.std.StdStatus; import org.apache.openaz.xacml.std.StdStatusCode; import org.apache.openaz.xacml.std.pip.StdMutablePIPResponse; import org.apache.openaz.xacml.std.pip.StdPIPResponse; import org.apache.openaz.xacml.std.pip.engines.StdConfigurableEngine; import org.apache.openaz.xacml.util.AttributeUtils; import org.apache.openaz.xacml.util.FactoryException; import au.com.bytecode.opencsv.CSVReader; import com.google.common.base.Splitter; public class CSVEngine extends StdConfigurableEngine { protected Log logger = LogFactory.getLog(this.getClass()); /* * Files that are smaller than this number are read into memory during startup. Larger files are read one * line at a time as needed to avoid overloading the JVM memory limit. */ public static final long DEFAULT_MAX_FILE_SIZE_FOR_READALL = 100000000; public static final String PROP_CLASSNAME = "classname"; public static final String PROP_MAXSIZE = "maxsize"; public static final String PROP_SOURCE = "source"; public static final String PROP_DELIMITER = "delimiter"; public static final String PROP_QUOTE = "quote"; public static final String PROP_SKIP = "skip"; public static final String PROP_RESOLVERS = "resolvers"; public static final String PROP_RESOLVER = "resolver"; private static DataTypeFactory dataTypeFactory = null; static { try { dataTypeFactory = DataTypeFactory.newInstance(); } catch (FactoryException fx) { throw new RuntimeException(fx); } } // // Values read from the properties file for use in managing the CSV file // private long maximumSize = DEFAULT_MAX_FILE_SIZE_FOR_READALL; private File csvSourceFile; private char csvDelimiter; private char csvQuote; private int csvSkip; // // big files must be read one line at a time; small files are read in all at once // private boolean fileIsBig = false; // // small files get all lines read at once into this list // private List<String[]> allLines = null; // // Our list of resolvers // private List<CSVResolver> csvResolvers = new ArrayList<CSVResolver>(); public CSVEngine() { } /** * Helper to read the top-level propertied for the CSV file as-a-whole * * @param id * @param properties * @throws org.apache.openaz.xacml.api.pip.PIPException */ private void readCSVFileConfiguration(String id, Properties properties) throws PIPException { String prefix = id + "."; // // Is there a max filesize to read into memory? // String maxSize = properties.getProperty(prefix + PROP_MAXSIZE, Long.toString(DEFAULT_MAX_FILE_SIZE_FOR_READALL)); try { this.maximumSize = Long.parseLong(maxSize); } catch (NumberFormatException e) { String message = this.getName() + ": The maximum size specified is NOT parseable: " + e.getLocalizedMessage(); this.logger.error(message); this.maximumSize = DEFAULT_MAX_FILE_SIZE_FOR_READALL; } // // Get the file source // String sourcePathString = properties.getProperty(prefix + PROP_SOURCE); if (sourcePathString == null || sourcePathString.length() == 0) { String message = this.getName() + ": No csv.source parameter given"; logger.error(message); throw new PIPException(message); } // // Now check the size of that file (and if it exists) // csvSourceFile = new File(sourcePathString); if (!csvSourceFile.exists() || csvSourceFile.length() == 0) { String message = this.getName() + ": The csv.source '" + csvSourceFile.getAbsolutePath() + "' does not exist or has no content"; logger.error(message); throw new PIPException(message); } if (csvSourceFile.length() > this.maximumSize) { if (logger.isDebugEnabled()) { logger.debug("File size is greater than max allowed (" + this.maximumSize + "): " + csvSourceFile.length()); } fileIsBig = true; } // // Get the properties for CSVReader // String tmpString = properties.getProperty(prefix + PROP_DELIMITER); if (tmpString == null || tmpString.length() != 1) { String message = this.getName() + ": The csv.delimiter must exist and be exactly 1 character"; logger.error(message); throw new PIPException(message); } csvDelimiter = tmpString.charAt(0); tmpString = properties.getProperty(prefix + PROP_QUOTE); if (tmpString == null || tmpString.length() != 1) { String message = this.getName() + ": The csv.quote must exist and be exactly 1 character"; logger.error(message); throw new PIPException(message); } csvQuote = tmpString.charAt(0); tmpString = properties.getProperty(prefix + PROP_SKIP); if (tmpString == null) { String message = this.getName() + ": The csv.skip must be set"; logger.error(message); throw new PIPException(message); } try { csvSkip = Integer.parseInt(tmpString); } catch (NumberFormatException e) { String message = this.getName() + ": The csv.skip value of '" + tmpString + "' cannot be converted to integer"; logger.error(message); throw new PIPException(message); } } @Override public void configure(String id, Properties properties) throws PIPException { // // Get our standard configurable properties // super.configure(id, properties); // // Get the properties this class cares about // this.readCSVFileConfiguration(id, properties); // // Get resolvers // String propResolverPrefix = id + "." + PROP_RESOLVERS; String stringProp = properties.getProperty(propResolverPrefix); if (stringProp == null || stringProp.isEmpty()) { this.logger.error("No '" + propResolverPrefix + "' property"); throw new PIPException("No '" + propResolverPrefix + "' property"); } // // Go through all our resolvers // for (String resolverId : Splitter.on(',').trimResults().omitEmptyStrings().split(stringProp)) { this.createResolver(id + "." + PROP_RESOLVER + "." + resolverId, properties); } // // If the file is small, we read it fully into memory. // if (!this.fileIsBig) { try (CSVReader csvReader = new CSVReader(new FileReader(csvSourceFile), csvDelimiter, csvQuote, csvSkip)) { this.allLines = csvReader.readAll(); if (logger.isDebugEnabled()) { logger.debug(id + ": All lines read from csv file, size=" + allLines.size()); } } catch (IOException e) { String message = id + ": CSVReader unable to read csv.source '" + csvSourceFile.getAbsolutePath() + "': " + e; logger.error(message, e); throw new PIPException(message); } } } /** * Creates a new {@link org.apache.openaz.xacml.std.pip.engines.csv.CSVResolver} by looking up the * "classname" property for the given <code>String</code> resolver ID and then calling its * <code>configure</code> method. * * @param resolverId the <code>String</code> identifier of the resolver to configure * @param properties the <code>Properties</code> to search for the "classname" and any resolver-specific * properties * @throws org.apache.openaz.xacml.api.pip.PIPException if there is an error creating the * <code>CSVResolver</code>. */ protected void createResolver(String resolverId, Properties properties) throws PIPException { String propPrefix = resolverId + "."; String resolverClassName = properties.getProperty(propPrefix + PROP_CLASSNAME); if (resolverClassName == null || resolverClassName.length() == 0) { this.logger.error("No '" + propPrefix + PROP_CLASSNAME + "' property."); throw new PIPException("No '" + propPrefix + PROP_CLASSNAME + "' property."); } try { Class<?> resolverClass = Class.forName(resolverClassName); if (!CSVResolver.class.isAssignableFrom(resolverClass)) { this.logger.error("CSVResolver class " + propPrefix + " does not implement " + CSVResolver.class.getCanonicalName()); throw new PIPException("CSVResolver class " + propPrefix + " does not implement " + CSVResolver.class.getCanonicalName()); } // // Try to create the resolver // CSVResolver csvResolver = CSVResolver.class.cast(resolverClass.newInstance()); // // Make sure it can configure itself // csvResolver.configure(resolverId, properties, this.getIssuer()); // // Good // this.csvResolvers.add(csvResolver); } catch (Exception ex) { this.logger.error("Exception creating CSVResolver: " + ex.getMessage(), ex); throw new PIPException("Exception creating CSVResolver", ex); } } @Override public PIPResponse getAttributes(PIPRequest pipRequest, PIPFinder pipFinder) throws PIPException { // // Do we have any resolvers defined? // if (this.csvResolvers.size() == 0) { throw new IllegalStateException(this.getClass().getCanonicalName() + " is not configured"); } // // Do any of our resolvers support this? // List<CSVResolver> resolvers = new ArrayList<CSVResolver>(); for (CSVResolver resolver : this.csvResolvers) { if (resolver.supportRequest(pipRequest)) { resolvers.add(resolver); } } if (resolvers.size() == 0) { if (this.logger.isDebugEnabled()) { this.logger.debug("does not support this pip request: " + pipRequest); } return StdPIPResponse.PIP_RESPONSE_EMPTY; } if (this.logger.isDebugEnabled()) { this.logger.debug("supports this pip request: " + pipRequest); } // // We have at least one, have the resolvers prepare themselves. // List<Map<Integer, List<AttributeValue<?>>>> listParameters = new ArrayList<Map<Integer, List<AttributeValue<?>>>>(); for (CSVResolver resolver : resolvers) { Map<Integer, List<AttributeValue<?>>> map = resolver.getColumnParameterValues(this, pipRequest, pipFinder); // // If the resolver cannot find all its parameter values, then we // if (map == null || map.isEmpty()) { this.logger.warn("Resolver could not find parameters."); return StdPIPResponse.PIP_RESPONSE_EMPTY; } listParameters.add(map); } // // Look at each line of the file to see if it matches the (non-unique) criteria in the parameters // and add the value in the associated column from the CSV file to the list of response Attributes. // StdMutablePIPResponse mutablePIPResponse = new StdMutablePIPResponse(); // // for smaller files, this is the index in the allLines List // int lineIndex = 0; // // for big files we need to read one line at a time from the CSVReader // CSVReader csvReader = null; try { if (this.fileIsBig) { csvReader = new CSVReader(new FileReader(csvSourceFile), csvDelimiter, csvQuote, csvSkip); } while (true) { String[] line = null; if (this.fileIsBig) { line = csvReader.readNext(); if (line == null) { // end of file break; } } else { if (lineIndex < this.allLines.size()) { line = this.allLines.get(lineIndex); lineIndex++; } else { // // end of (previously-read) list // break; } } // // Does the line match? // if (!this.doesLineMatch(line, listParameters)) { continue; } // // Ask each resolver to return any attributes from the line // for (CSVResolver resolver : resolvers) { List<Attribute> attributes = resolver.decodeResult(line); if (attributes != null && attributes.size() > 0) { if (this.logger.isDebugEnabled()) { this.logger.debug("resolver returned " + attributes.size() + " attributes"); } mutablePIPResponse.addAttributes(attributes); } } } // // Done reading the file // if (this.logger.isDebugEnabled()) { this.logger.debug("Returning " + mutablePIPResponse.getAttributes().size() + " attributes"); for (Attribute attribute : mutablePIPResponse.getAttributes()) { this.logger.debug(System.lineSeparator() + AttributeUtils.prettyPrint(attribute)); } } return new StdPIPResponse(mutablePIPResponse); } catch (Exception e) { String message = this.getName() + ": Error processing line: " + e; logger.error(message, e); return new StdPIPResponse(new StdStatus(StdStatusCode.STATUS_CODE_PROCESSING_ERROR, e.getMessage())); } finally { if (csvReader != null) { try { csvReader.close(); } catch (IOException e) { this.logger.error("Close CSV Reader: " + e.getLocalizedMessage()); } } } } protected boolean doesLineMatch(String[] line, List<Map<Integer, List<AttributeValue<?>>>> listParameters) { for (Map<Integer, List<AttributeValue<?>>> map : listParameters) { for (Integer column : map.keySet()) { // // Sanity check, we should have the required number of columns // if (column >= line.length) { return false; } // // Does it have a value? // String lineValue = line[column]; if (lineValue.length() == 0) { return false; } // // Now check the value // boolean foundMatch = false; for (AttributeValue<?> value : map.get(column)) { DataType<?> dt = dataTypeFactory.getDataType(value.getDataTypeId()); try { // // Convert the value. NOTE: This may be time-consuming as opposed to // converting the parameter value to a String once and then doing string // comparisons. But, using a CSV is already a performance issue and should // only be used for testing purposes or very small data sets. Use of a cache // can help performance if the CSV file is necessary in a production environment. // Object convertedValue = dt.convert(lineValue); if (convertedValue.equals(value.getValue())) { // // It matches // foundMatch = true; break; } } catch (DataTypeException e) { String message = column + " could not convert lineValue to " + dt.getId(); this.logger.error(message); } } // // Did a match happen? // if (!foundMatch) { /* * if (this.logger.isDebugEnabled()) { * this.logger.debug("Failed to find value for column " + column); } */ return false; } } } // // If we get here, we found at least one match for all the // parameters. // return true; } @Override public Collection<PIPRequest> attributesRequired() { Set<PIPRequest> requiredAttributes = new HashSet<PIPRequest>(); for (CSVResolver resolver : this.csvResolvers) { resolver.attributesRequired(requiredAttributes); } return requiredAttributes; } @Override public Collection<PIPRequest> attributesProvided() { Set<PIPRequest> attributes = new HashSet<PIPRequest>(); for (CSVResolver resolver : this.csvResolvers) { resolver.attributesProvided(attributes); } return attributes; } }