001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2010–2011 ApexIdentity Inc. 015 * Portions Copyright 2011-2014 ForgeRock AS. 016 */ 017 018package org.forgerock.openig.filter; 019 020import static org.forgerock.openig.util.Json.*; 021 022import java.io.File; 023import java.io.IOException; 024import java.util.Map; 025 026import org.forgerock.openig.el.Expression; 027import org.forgerock.openig.handler.Handler; 028import org.forgerock.openig.handler.HandlerException; 029import org.forgerock.openig.heap.GenericHeaplet; 030import org.forgerock.openig.heap.HeapException; 031import org.forgerock.openig.http.Exchange; 032import org.forgerock.openig.text.SeparatedValuesFile; 033import org.forgerock.openig.text.Separators; 034import org.forgerock.util.Factory; 035import org.forgerock.util.LazyMap; 036 037/** 038 * Retrieves and exposes a record from a delimier-separated file. Lookup of the record is 039 * performed using a specified key, whose value is derived from an exchange-scoped expression. 040 * The resulting record is exposed in a {@link Map} object, whose location is specified by the 041 * {@code target} expression. If a matching record cannot be found, then the resulting map 042 * will be empty. 043 * <p> 044 * The retrieval of the record is performed lazily; it does not occur until the first attempt 045 * to access a value in the target. This defers the overhead of file operations and text 046 * processing until a value is first required. This also means that the {@code value} 047 * expression will not be evaluated until the map is first accessed. 048 * 049 * @see SeparatedValuesFile 050 */ 051public class FileAttributesFilter extends GenericFilter { 052 053 /** Expression that yields the target object that will contain the record. */ 054 private final Expression target; 055 056 /** The file to read separated values from. */ 057 private final SeparatedValuesFile file; 058 059 /** The name of the field in the file to perform the lookup on. */ 060 private final String key; 061 062 /** Expression that yields the value to be looked-up within the file. */ 063 private final Expression value; 064 065 /** 066 * Builds a new FileAttributesFilter extracting values from the given separated values file. 067 * 068 * @param file 069 * The file to read separated values from ({@literal csv} file) 070 * @param key 071 * The name of the field in the file to perform the lookup on 072 * @param value 073 * Expression that yields the value to be looked-up within the file 074 * @param target 075 * Expression that yields the target object that will contain the record 076 */ 077 public FileAttributesFilter(final SeparatedValuesFile file, 078 final String key, 079 final Expression value, 080 final Expression target) { 081 this.file = file; 082 this.key = key; 083 this.value = value; 084 this.target = target; 085 } 086 087 @Override 088 public void filter(final Exchange exchange, Handler next) throws HandlerException, IOException { 089 target.set(exchange, new LazyMap<String, String>(new Factory<Map<String, String>>() { 090 @Override 091 public Map<String, String> newInstance() { 092 try { 093 return file.getRecord(key, value.eval(exchange).toString()); 094 } catch (IOException ioe) { 095 logger.warning(ioe); 096 // results in an empty map 097 return null; 098 } 099 } 100 })); 101 next.handle(exchange); 102 } 103 104 /** Creates and initializes a separated values file attribute provider in a heap environment. */ 105 public static class Heaplet extends GenericHeaplet { 106 @Override 107 public Object create() throws HeapException { 108 SeparatedValuesFile sources = new SeparatedValuesFile(new File(evaluate(config.get("file").required())), 109 config.get("charset").defaultTo("UTF-8").asCharset(), 110 config.get("separator").defaultTo("COMMA") 111 .asEnum(Separators.class).getSeparator(), 112 config.get("header").defaultTo(true).asBoolean()); 113 114 if (config.isDefined("fields")) { 115 sources.getFields().addAll(config.get("fields").asList(String.class)); 116 } 117 return new FileAttributesFilter(sources, 118 config.get("key").required().asString(), 119 asExpression(config.get("value").required()), 120 asExpression(config.get("target").required())); 121 } 122 } 123}