001/*
002 *  Copyright 2016 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.web.site;
017
018import java.io.File;
019import java.io.FileInputStream;
020import java.io.FileNotFoundException;
021import java.io.IOException;
022import java.io.InputStream;
023import java.nio.file.Files;
024import java.nio.file.Paths;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032import java.util.stream.Collectors;
033
034import org.apache.avalon.framework.service.ServiceException;
035import org.apache.avalon.framework.service.ServiceManager;
036import org.apache.cocoon.ProcessingException;
037import org.apache.cocoon.generation.ServiceableGenerator;
038import org.apache.cocoon.xml.AttributesImpl;
039import org.apache.cocoon.xml.XMLUtils;
040import org.apache.commons.lang3.tuple.Pair;
041import org.apache.excalibur.xml.sax.SAXParser;
042import org.xml.sax.InputSource;
043import org.xml.sax.SAXException;
044
045import org.ametys.core.authentication.CredentialProvider;
046import org.ametys.core.authentication.CredentialProviderFactory;
047import org.ametys.core.authentication.CredentialProviderModel;
048import org.ametys.core.datasource.LDAPDataSourceManager;
049import org.ametys.core.datasource.SQLDataSourceManager;
050import org.ametys.core.user.directory.UserDirectory;
051import org.ametys.core.user.directory.UserDirectoryFactory;
052import org.ametys.core.user.directory.UserDirectoryModel;
053import org.ametys.core.user.population.PopulationContextHelper;
054import org.ametys.core.user.population.UserPopulation;
055import org.ametys.core.user.population.UserPopulationDAO;
056import org.ametys.core.util.IgnoreRootHandler;
057import org.ametys.runtime.config.Config;
058import org.ametys.runtime.model.ElementDefinition;
059import org.ametys.runtime.model.type.ModelItemTypeConstants;
060import org.ametys.web.repository.site.SiteManager;
061
062/**
063 * Sax the datasources files limited to datasources useful for front-office
064 */
065public class SitesPopulationsGenerator extends ServiceableGenerator
066{
067    private SiteManager _siteManager;
068    private PopulationContextHelper _populationContextHelper;
069    private UserPopulationDAO _userPopulationDAO;
070    private UserDirectoryFactory _userDirectoryFactory;
071    private CredentialProviderFactory _credentialProviderFactory;
072    private SQLDataSourceManager _sqlDataSourceManager;
073    private LDAPDataSourceManager _ldapDataSourceManager;
074
075    @Override
076    public void service(ServiceManager smanager) throws ServiceException
077    {
078        super.service(smanager);
079        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
080        _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE);
081        _userPopulationDAO = (UserPopulationDAO) manager.lookup(UserPopulationDAO.ROLE);
082        _userDirectoryFactory = (UserDirectoryFactory) manager.lookup(UserDirectoryFactory.ROLE);
083        _credentialProviderFactory = (CredentialProviderFactory) manager.lookup(CredentialProviderFactory.ROLE);
084        _sqlDataSourceManager = (SQLDataSourceManager) manager.lookup(SQLDataSourceManager.ROLE);
085        _ldapDataSourceManager = (LDAPDataSourceManager) manager.lookup(LDAPDataSourceManager.ROLE);
086    }
087    
088    public void generate() throws IOException, SAXException, ProcessingException
089    {
090        contentHandler.startDocument();
091        XMLUtils.startElement(contentHandler, "Populations");
092        
093        _saxUsedStuff();
094        _saxPopulationFile();
095        _saxDatabasesFiles();
096        _saxMonitoringInfo();
097        _saxCaptchaInfo();
098        
099        XMLUtils.endElement(contentHandler, "Populations");
100        contentHandler.endDocument();
101    }
102    
103    private void _saxMonitoringInfo() throws SAXException
104    {
105        boolean enabled = Config.getInstance().getValue("cache.monitoring.schedulers.enable");
106        
107        AttributesImpl attrs = new AttributesImpl();
108        attrs.addCDATAAttribute("enabled", enabled ? "true" : "false");
109        XMLUtils.startElement(contentHandler, "Monitoring", attrs);
110
111        if (enabled)
112        {
113            String datasourceId = Config.getInstance().getValue("cache.monitoring.datasource.jdbc.pool");
114            XMLUtils.createElement(contentHandler, "Datasource", _replaceDefaultIds(datasourceId));
115        }
116        
117        XMLUtils.endElement(contentHandler, "Monitoring");
118    }
119    
120    private void _saxCaptchaInfo() throws SAXException
121    {
122        String type = Config.getInstance().getValue("runtime.captcha.type");
123        String publicKey = Config.getInstance().getValue("runtime.captcha.recaptcha.publickey");
124        String secretKey = Config.getInstance().getValue("runtime.captcha.recaptcha.secretkey");
125        
126        AttributesImpl attrs = new AttributesImpl();
127        attrs.addCDATAAttribute("type", type);
128        attrs.addCDATAAttribute("publicKey", publicKey);
129        attrs.addCDATAAttribute("secretKey", secretKey);
130        
131        XMLUtils.createElement(contentHandler, "Captcha", attrs);
132    }
133
134    private void _saxDatabasesFiles() throws IOException, SAXException
135    {
136        _saxSQLDatabaseFile();
137
138        _saxLDAPDatabaseFile();
139    }
140
141    private void _saxLDAPDatabaseFile() throws SAXException, IOException
142    {
143        XMLUtils.startElement(contentHandler, "LDAPDatasources");
144
145        File file = _ldapDataSourceManager.getFileConfiguration();
146        if (file.exists())
147        {
148            SAXParser saxParser = null;
149            try (InputStream is = new FileInputStream(file))
150            {
151                saxParser = (SAXParser) manager.lookup(SAXParser.ROLE);
152                saxParser.parse(new InputSource(is), new IgnoreRootHandler(contentHandler));
153            }
154            catch (FileNotFoundException e)
155            {
156                throw new IOException(e);
157            }
158            catch (ServiceException e)
159            {
160                throw new SAXException("Unable to get a SAX parser", e);
161            }
162            finally
163            {
164                manager.release(saxParser);
165            }
166        }
167
168        XMLUtils.endElement(contentHandler, "LDAPDatasources");
169    }
170
171    private void _saxSQLDatabaseFile() throws SAXException, IOException
172    {
173        XMLUtils.startElement(contentHandler, "SQLDatasources");
174
175        File file = _sqlDataSourceManager.getFileConfiguration();
176        if (file.exists())
177        {
178            SAXParser saxParser = null;
179            try (InputStream is = new FileInputStream(file))
180            {
181                saxParser = (SAXParser) manager.lookup(SAXParser.ROLE);
182                saxParser.parse(new InputSource(is), new IgnoreRootHandler(contentHandler));
183            }
184            catch (FileNotFoundException e)
185            {
186                throw new IOException(e);
187            }
188            catch (ServiceException e)
189            {
190                throw new SAXException("Unable to get a SAX parser", e);
191            }
192            finally
193            {
194                manager.release(saxParser);
195            }
196        }
197        
198        XMLUtils.endElement(contentHandler, "SQLDatasources");
199    }
200
201    private void _saxPopulationFile() throws SAXException, IOException
202    {
203        XMLUtils.startElement(contentHandler, "UserPopulations");
204        File file = _userPopulationDAO.getConfigurationFile();
205
206        if (file != null && file.exists())
207        {
208            SAXParser saxParser = null;
209            try (InputStream is = Files.newInputStream(Paths.get(file.getAbsolutePath())))
210            {
211                saxParser = (SAXParser) manager.lookup(SAXParser.ROLE);
212                saxParser.parse(new InputSource(is), new IgnoreRootHandler(contentHandler));
213            }
214            catch (ServiceException e)
215            {
216                throw new SAXException("Unable to get a SAX parser", e);
217            }
218            finally
219            {
220                manager.release(saxParser);
221            }
222        }
223        
224        XMLUtils.endElement(contentHandler, "UserPopulations");
225    }
226
227    private void _saxUsedStuff() throws SAXException
228    {
229        Set<UserPopulation> usedPopulations = _getPopulationsUsedBySites();
230        
231        XMLUtils.startElement(contentHandler, "InUse");
232        
233        _saxPopulationsInUse(usedPopulations);
234        
235        _saxDatasourcesInUse(usedPopulations);
236
237        XMLUtils.endElement(contentHandler, "InUse");
238    }
239
240    private void _saxDatasourcesInUse(Set<UserPopulation> usedPopulations) throws SAXException
241    {
242        Pair<Set<String>, Map<String, Set<String>>> usedDatasources = _getDatasourcesUsedByPopulations(usedPopulations);
243        
244        XMLUtils.startElement(contentHandler, "Datasources");
245        
246        for (String datasourceId : usedDatasources.getLeft())
247        {
248            XMLUtils.createElement(contentHandler, "Datasource", _replaceDefaultIds(datasourceId));
249        }
250        
251        XMLUtils.endElement(contentHandler, "Datasources");
252        
253        XMLUtils.startElement(contentHandler, "Datasources-Model");
254        
255        Map<String, Set<String>> datasourcesPerModel = usedDatasources.getRight();
256        for (String modelId : datasourcesPerModel.keySet())
257        {
258            XMLUtils.startElement(contentHandler, modelId);
259            
260            for (String parameter : datasourcesPerModel.get(modelId))
261            {
262                XMLUtils.createElement(contentHandler, parameter);
263            }
264            
265            XMLUtils.endElement(contentHandler, modelId);
266        }
267        
268        XMLUtils.endElement(contentHandler, "Datasources-Model");
269    }
270
271    private void _saxPopulationsInUse(Set<UserPopulation> usedPopulations) throws SAXException
272    {
273        XMLUtils.startElement(contentHandler, "UserPopulations");
274        for (UserPopulation userPopulation : usedPopulations)
275        {
276            XMLUtils.createElement(contentHandler, "UserPopulation", userPopulation.getId());
277        }
278        XMLUtils.endElement(contentHandler, "UserPopulations");
279    }
280
281    private String _replaceDefaultIds(String datasourceId)
282    {
283        // Default sources won't be default anymore => change it
284        if (_ldapDataSourceManager.getDefaultDataSourceId().equals(datasourceId))
285        {
286            return _ldapDataSourceManager.getDefaultDataSourceDefinition().getId();
287        }
288        else if (_sqlDataSourceManager.getDefaultDataSourceId().equals(datasourceId))
289        {
290            return _sqlDataSourceManager.getDefaultDataSourceDefinition().getId();
291        }
292        else
293        {
294            return datasourceId;
295        }
296    }
297
298    private Set<UserPopulation> _getPopulationsUsedBySites()
299    {
300        // Retrieve the sites to build the contexts to search on
301        Collection<String> siteNames = _siteManager.getSiteNames();
302        
303        // We return all the populations linked to at least one site
304        List<String> contexts = new ArrayList<>();
305        for (String siteName : siteNames)
306        {
307            contexts.add("/sites/" + siteName);
308            contexts.add("/sites-fo/" + siteName);
309        }
310        
311        Set<String> populations = _populationContextHelper.getUserPopulationsOnContexts(contexts, false, false);
312        return populations.stream().map(_userPopulationDAO::getUserPopulation).collect(Collectors.toSet());
313    }
314    
315    private Pair<Set<String>, Map<String, Set<String>>> _getDatasourcesUsedByPopulations(Set<UserPopulation> usedPopulations)
316    {
317        Set<String> datasourcesInUse = new HashSet<>();
318        Map<String, Set<String>> datasourcesPerModel = new HashMap<>();
319        
320        for (UserPopulation userPopulation : usedPopulations)
321        {
322            for (UserDirectory userDirectory : userPopulation.getUserDirectories())
323            {
324                String userDirectoryModelId = userDirectory.getUserDirectoryModelId();
325                UserDirectoryModel userDirectoryModel = _userDirectoryFactory.getExtension(userDirectoryModelId);
326                
327                Map<String, Object> parameterValues = userDirectory.getParameterValues();
328                
329                Map<String, ? extends ElementDefinition> userDirectoryModelParameters = userDirectoryModel.getParameters();
330                for (String userDirectoryModelParameterId : userDirectoryModelParameters.keySet())
331                {
332                    ElementDefinition userDirectoryModelParameter = userDirectoryModelParameters.get(userDirectoryModelParameterId);
333                    if (ModelItemTypeConstants.DATASOURCE_ELEMENT_TYPE_ID.equals(userDirectoryModelParameter.getType().getId()))
334                    {
335                        String datasourceId = (String) parameterValues.get(userDirectoryModelParameterId);
336                        datasourcesInUse.add(datasourceId);
337                        _addDatasourceToModel(userDirectoryModelId, userDirectoryModelParameterId, datasourcesPerModel);
338                    }
339                }
340            }
341            
342            for (CredentialProvider credentialProvider : userPopulation.getCredentialProviders())
343            {
344                String credentialProviderModelId = credentialProvider.getCredentialProviderModelId();
345                CredentialProviderModel credentialProviderModel = _credentialProviderFactory.getExtension(credentialProviderModelId);
346                
347                Map<String, Object> parameterValues = credentialProvider.getParameterValues();
348                
349                Map<String, ? extends ElementDefinition> credentialProviderModelParameters = credentialProviderModel.getParameters();
350                for (String credentialProviderParameterId : credentialProviderModelParameters.keySet())
351                {
352                    ElementDefinition credentialProviderModelParameter = credentialProviderModelParameters.get(credentialProviderParameterId);
353                    if (ModelItemTypeConstants.DATASOURCE_ELEMENT_TYPE_ID.equals(credentialProviderModelParameter.getType().getId()))
354                    {
355                        String datasourceId = (String) parameterValues.get(credentialProviderParameterId);
356                        datasourcesInUse.add(datasourceId);
357                        _addDatasourceToModel(credentialProviderModelId, credentialProviderParameterId, datasourcesPerModel);
358                    }
359                }
360            }
361        }
362        
363        return Pair.of(datasourcesInUse, datasourcesPerModel);
364    }
365    
366    private void _addDatasourceToModel(String modelId, String datasource, Map<String, Set<String>> datasourcesPerModel)
367    {
368        Set<String> datasources = datasourcesPerModel.get(modelId);
369        if (datasources == null)
370        {
371            datasources = new HashSet<>(); 
372            datasourcesPerModel.put(modelId, datasources);
373        }
374        
375        datasources.add(datasource);
376    }
377}