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-2015 ForgeRock AS. 016 */ 017 018package org.forgerock.openig.filter; 019 020import static java.lang.String.format; 021import static org.forgerock.openig.el.Bindings.bindings; 022import static org.forgerock.openig.util.JsonValues.asExpression; 023import static org.forgerock.openig.util.JsonValues.evaluate; 024 025import java.io.File; 026import java.io.IOException; 027import java.util.Collections; 028import java.util.Map; 029 030import org.forgerock.http.Filter; 031import org.forgerock.http.Handler; 032import org.forgerock.http.protocol.Request; 033import org.forgerock.http.protocol.Response; 034import org.forgerock.openig.el.Bindings; 035import org.forgerock.openig.el.Expression; 036import org.forgerock.openig.heap.GenericHeapObject; 037import org.forgerock.openig.heap.GenericHeaplet; 038import org.forgerock.openig.heap.HeapException; 039import org.forgerock.openig.text.SeparatedValuesFile; 040import org.forgerock.openig.text.Separators; 041import org.forgerock.services.context.Context; 042import org.forgerock.util.Factory; 043import org.forgerock.util.LazyMap; 044import org.forgerock.util.promise.NeverThrowsException; 045import org.forgerock.util.promise.Promise; 046 047/** 048 * Retrieves and exposes a record from a delimiter-separated file. Lookup of the record is 049 * performed using a specified key, whose value is derived from an expression. 050 * The resulting record is exposed in a {@link Map} object, whose location is specified by the 051 * {@code target} expression. If a matching record cannot be found, then the resulting map 052 * will be empty. 053 * <p> 054 * The retrieval of the record is performed lazily; it does not occur until the first attempt 055 * to access a value in the target. This defers the overhead of file operations and text 056 * processing until a value is first required. This also means that the {@code value} 057 * expression will not be evaluated until the map is first accessed. 058 * 059 * @see SeparatedValuesFile 060 */ 061public class FileAttributesFilter extends GenericHeapObject implements Filter { 062 063 /** Expression that yields the target object that will contain the record. */ 064 @SuppressWarnings("rawtypes") // Can't find the correct syntax to write Expression<Map<String, String>> 065 private final Expression<Map> target; 066 067 /** The file to read separated values from. */ 068 private final SeparatedValuesFile file; 069 070 /** The name of the field in the file to perform the lookup on. */ 071 private final String key; 072 073 /** Expression that yields the value to be looked-up within the file. */ 074 private final Expression<String> value; 075 076 /** 077 * Builds a new FileAttributesFilter extracting values from the given separated values file. 078 * 079 * @param file 080 * The file to read separated values from ({@literal csv} file) 081 * @param key 082 * The name of the field in the file to perform the lookup on 083 * @param value 084 * Expression that yields the value to be looked-up within the file 085 * @param target 086 * Expression that yields the target object that will contain the record 087 */ 088 public FileAttributesFilter(final SeparatedValuesFile file, 089 final String key, 090 final Expression<String> value, 091 @SuppressWarnings("rawtypes") final Expression<Map> target) { 092 this.file = file; 093 this.key = key; 094 this.value = value; 095 this.target = target; 096 } 097 098 @Override 099 public Promise<Response, NeverThrowsException> filter(final Context context, 100 final Request request, 101 final Handler next) { 102 final Bindings bindings = bindings(context, request); 103 target.set(bindings, new LazyMap<>(new Factory<Map<String, String>>() { 104 @Override 105 public Map<String, String> newInstance() { 106 try { 107 String eval = value.eval(bindings); 108 Map<String, String> record = file.getRecord(key, eval); 109 if (record == null) { 110 logger.debug(format("Couldn't select a row where column %s value is equal to %s", key, eval)); 111 return Collections.emptyMap(); 112 } else { 113 return record; 114 } 115 } catch (IOException ioe) { 116 logger.warning(ioe); 117 // results in an empty map 118 return Collections.emptyMap(); 119 } 120 } 121 })); 122 return next.handle(context, request); 123 } 124 125 /** Creates and initializes a separated values file attribute provider in a heap environment. */ 126 public static class Heaplet extends GenericHeaplet { 127 @Override 128 public Object create() throws HeapException { 129 String filename = evaluate(config.get("file").required()); 130 SeparatedValuesFile sources = new SeparatedValuesFile(new File(filename), 131 config.get("charset").defaultTo("UTF-8").asCharset(), 132 config.get("separator").defaultTo("COMMA") 133 .asEnum(Separators.class).getSeparator(), 134 config.get("header").defaultTo(true).asBoolean()); 135 136 if (config.isDefined("fields")) { 137 sources.getFields().addAll(config.get("fields").asList(String.class)); 138 } 139 return new FileAttributesFilter(sources, 140 config.get("key").required().asString(), 141 asExpression(config.get("value").required(), String.class), 142 asExpression(config.get("target").required(), Map.class)); 143 } 144 } 145}