001/*
002 *  Copyright 2010 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.externaldata.data.ldap;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.List;
023import java.util.Map;
024
025import javax.naming.directory.SearchControls;
026import javax.naming.directory.SearchResult;
027
028import org.apache.commons.lang.StringUtils;
029
030import org.ametys.core.datasource.DataSourceClientInteraction.DataSourceType;
031import org.ametys.core.util.ldap.AbstractLDAPConnector;
032import org.ametys.core.util.ldap.ScopeEnumerator;
033import org.ametys.plugins.externaldata.data.DataInclusionException;
034import org.ametys.plugins.externaldata.data.DataSourceFactory;
035import org.ametys.plugins.externaldata.data.Query.ResultType;
036import org.ametys.runtime.plugin.component.PluginAware;
037
038/**
039 * LDAP Data Source factory.
040 */
041public class LdapDataSourceFactory extends AbstractLDAPConnector implements DataSourceFactory<LdapQuery, LdapQueryResult>, PluginAware
042{
043    /** The relative DN for users */
044    public static final String QUERY_CONFIGURATION_RELATIVE_DN = "relativeDN";
045    
046    /** The search scope of the query */
047    public static final String QUERY_CONFIGURATION_SCOPE = "scope";
048    
049    /** The LDAp attributes to return */
050    public static final String QUERY_CONFIGURATION_ATTRIBUTES = "attributes";
051    
052    /** Constraint query parameter. */
053    public static final String QUERY_CONFIGURATION_CONSTRAINT = "constraint";
054    
055    /** Query configuration parameters. */
056    public static final List<String> QUERY_CONFIGURATION_PARAMETERS = Arrays.asList(
057        QUERY_CONFIGURATION_RELATIVE_DN,
058        QUERY_CONFIGURATION_SCOPE,
059        QUERY_CONFIGURATION_ATTRIBUTES,
060        QUERY_CONFIGURATION_CONSTRAINT);
061    
062    private String _id;
063    
064    @Override
065    public void setPluginInfo(String pluginName, String featureName, String id)
066    {
067        _id = id;
068    }
069    
070    @Override
071    public Collection<DataSourceType> getHandledTypes()
072    {
073        return Collections.singleton(DataSourceType.LDAP);
074    }
075    
076    
077    @Override
078    public Collection<String> getQueryConfigurationParameters(String type)
079    {
080        return QUERY_CONFIGURATION_PARAMETERS;
081    }
082    
083    
084    @Override
085    public LdapQuery buildQuery(String id, String type, String name, String description, ResultType resultType, String dataSourceId, Map<String, String> additionalConfiguration) throws DataInclusionException
086    {
087        String relativeDN = StringUtils.defaultString(additionalConfiguration.get(QUERY_CONFIGURATION_RELATIVE_DN));
088        String attributes = StringUtils.defaultString(additionalConfiguration.get(QUERY_CONFIGURATION_ATTRIBUTES));
089        String constraint = StringUtils.defaultString(additionalConfiguration.get(QUERY_CONFIGURATION_CONSTRAINT));
090        String scope = StringUtils.defaultString(additionalConfiguration.get(QUERY_CONFIGURATION_SCOPE));
091        
092//        if (StringUtils.isBlank(attributes))
093//        {
094//            throw new DataInclusionException("Impossible to build the LDAP query : returning attributes must be provided");
095//        }
096        
097        LdapQuery query = new LdapQuery();
098        
099        query.setId(id);
100        query.setFactory(this._id);
101        query.setName(name);
102        query.setDescription(description);
103        query.setResultType(resultType);
104        query.setDataSourceId(dataSourceId);
105        
106        query.setRelativeDN(relativeDN);
107        query.setAttributes(attributes);
108        query.setConstraint(constraint);
109        query.setScope(scope);
110        
111        return query;
112    }
113    
114    @Override
115    public LdapQueryResult execute(LdapQuery query, Map<String, String> parameterValues) throws DataInclusionException
116    {
117        return execute(query, parameterValues, 0, Integer.MAX_VALUE);
118    }
119    
120    public LdapQueryResult execute(LdapQuery query, Map<String, String> parameterValues, int offset, int limit) throws DataInclusionException
121    {
122        List<SearchResult> results = null;
123        try
124        {
125            String dataSourceId = query.getDataSourceId();
126            _delayedInitialize(dataSourceId);
127            
128            Map<String, String> attributeMap = query.getAttributesAsMap();
129            
130            int paramCount = query.getParameters().size();
131            Object[] values = new Object[paramCount];
132            
133            // Replace all the parameters by "{x}" placeholders and fill the parameter values array.
134            int paramIndex = 0;
135            String constraint = query.getConstraint();
136            for (String paramName : query.getParameters().keySet())
137            {
138                if (parameterValues.containsKey(paramName))
139                {
140                    constraint = constraint.replaceAll("\\$\\{" + paramName + "(\\[[^\\]]*\\])?\\}", "{" + paramIndex + "}");
141                    values[paramIndex] = parameterValues.get(paramName);
142                    paramIndex++;
143                }
144                else
145                {
146                    String regexp = "\\([^()=,\\s]*=[^$()]*\\$\\{" + paramName + "(\\[[^\\]]*\\])?\\}[^\\)]*\\)";
147                    constraint = constraint.replaceAll(regexp, "");
148                }
149            }
150            
151            // Search filter.
152            StringBuilder filter = new StringBuilder();
153            // filter.append("(&").append(_dataSource.getFilter());
154            if (StringUtils.isNotBlank(constraint))
155            {
156                filter.append(constraint);
157            }
158            
159            List<String> columnLabels = new ArrayList<>(attributeMap.keySet());
160            
161            // Create the controls : the attributes to return are the values of the map.
162            SearchControls controls = _getSearchControls(attributeMap.values(), query.getScope());
163            
164            // Execute the search.
165            results = _search(query.getRelativeDN(), filter.toString(), values, controls, offset, limit);
166            
167            // Extract and return the results.
168            LdapQueryResult queryResult = new LdapQueryResult(results, columnLabels, attributeMap);
169            
170            return queryResult;
171        }
172        catch (Exception e)
173        {
174            throw new DataInclusionException("Unable to execute the LDAP query.", e);
175        }
176    }
177    
178    /**
179     * Get the LDAP search controls.
180     * @param attributes the attributes to return.
181     * @param scopeStr the scope as a String.
182     * @return the search controls.
183     * @throws DataInclusionException If an error occurred
184     */
185    protected SearchControls _getSearchControls(Collection<String> attributes, String scopeStr) throws DataInclusionException
186    {
187        // Paramètres de recherche
188        SearchControls controls = new SearchControls();
189        
190        String[] attrArray = attributes.toArray(new String[attributes.size()]);
191        
192        controls.setReturningAttributes(attrArray);
193        
194        controls.setSearchScope(_getScope(scopeStr));
195        
196        return controls;
197    }
198    
199    /**
200     * Get the scope as an integer (handlable by the SearchControls) from the scope string.
201     * @param scopeStr the scope string.
202     * @return the scope as an integer.
203     * @throws DataInclusionException If an error occurred
204     */
205    protected int _getScope(String scopeStr) throws DataInclusionException
206    {
207        try
208        {
209            return ScopeEnumerator.parseScope(scopeStr);
210        }
211        catch (IllegalArgumentException e)
212        {
213            throw new DataInclusionException("Unable to parse scope", e);
214        }
215    }
216}