/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.csv2geofence;
import org.geoserver.csv2geofence.config.model.Configuration;
import org.geoserver.csv2geofence.config.model.GeofenceConfig;
import org.geoserver.csv2geofence.config.model.RuleFileConfig;
import org.geoserver.csv2geofence.config.model.UserFileConfig;
import org.geoserver.csv2geofence.config.model.internal.RuleOp;
import org.geoserver.csv2geofence.config.model.internal.RunInfo;
import org.geoserver.csv2geofence.config.model.internal.UserOp;
import org.geoserver.csv2geofence.impl.RuleFileLoader;
import org.geoserver.csv2geofence.impl.UserFileLoader;
import org.geoserver.csv2geofence.impl.RulesProcessor;
import org.geoserver.csv2geofence.impl.UsersProcessor;
import org.geoserver.geofence.services.dto.RuleFilter;
import org.geoserver.geofence.services.dto.ShortGroup;
import org.geoserver.geofence.services.rest.GeoFenceClient;
import org.geoserver.geofence.services.rest.RuleServiceHelper;
import org.geoserver.geofence.services.rest.model.RESTBatch;
import org.geoserver.geofence.services.rest.model.RESTBatchOperation;
import org.geoserver.geofence.services.rest.model.RESTInputRule;
import org.geoserver.geofence.services.rest.model.RESTInputUser;
import org.geoserver.geofence.services.rest.model.RESTOutputRule;
import org.geoserver.geofence.services.rest.model.RESTOutputRuleList;
import org.geoserver.geofence.services.rest.model.config.RESTFullUserGroupList;
import org.geoserver.geofence.services.rest.model.util.RESTBatchOperationFactory;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXB;
import org.apache.cxf.jaxrs.client.ServerWebApplicationException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Main logic.
*
* Invoked by the main() method.
*
* @author ETj (etj at geo-solutions.it)
*/
public class Runner {
private final static Logger LOGGER = LogManager.getLogger(Runner.class);
private RunInfo runInfo;
public Runner(RunInfo runInfo) {
this.runInfo = runInfo;
}
public void run() throws IOException {
Configuration cfg = JAXB.unmarshal(runInfo.getConfigurationFile(), Configuration.class);
GeoFenceClient geoFenceClient = createClient(cfg.getGeofenceConfig());
if(geoFenceClient == null)
System.exit(4);
//-- load existing groups from geofence
// map contains as key the uppercase version of the name of the group in the value
LOGGER.info("Loading existing groups in GeoFence...");
Map<String, String> existingGroups = retrieveGFGroups(geoFenceClient);
//-- gather information on requested groups from files
// map contains as key the uppercase version of the name of the group in the value
LOGGER.info("Scanning user files for groups...");
Map<String, String> requestedGroups = retrieveRequestedGroups(cfg.getUserFileConfig(), runInfo.getUserFiles());
RESTBatch batch = new RESTBatch();
// create new user groups
List<RESTBatchOperation> groupOps = createGroups(requestedGroups, existingGroups);
batch.getList().addAll(groupOps);
// Process the user files
LOGGER.info("Scanning user files for users...");
for (File file : runInfo.getUserFiles()) {
LOGGER.info("Processing user file '" + file+"'");
List<RESTBatchOperation> batchOps = processUserFile(file, cfg.getUserFileConfig(), existingGroups);
batch.getList().addAll(batchOps);
}
// Process the rule files
LOGGER.info("Scanning rule files...");
for (File file : runInfo.getRuleFiles()) {
LOGGER.info("Processing rule file '" + file+"'");
List<RESTBatchOperation> batchOps = processRuleFile(file, cfg.getRuleFileConfig(), existingGroups);
batch.getList().addAll(batchOps);
}
// verify users
LOGGER.info("Performing existence check on users...");
verifyUsers(batch, geoFenceClient);
LOGGER.info("Performing existence check on rules...");
verifyRules(batch, geoFenceClient);
if( runInfo.getOutputFile() != null) {
Writer xmlWriter = null;
xmlWriter = new FileWriter(runInfo.getOutputFile());
LOGGER.info("Creating XML command file " + runInfo.getOutputFile());
JAXB.marshal(batch, xmlWriter);
xmlWriter.flush();
xmlWriter.close();
LOGGER.info("XML command file saved.");
}
if(runInfo.isSendRequested()) {
LOGGER.info("Sending "+batch.getList().size()+" commands to GeoFence...");
try {
geoFenceClient.getBatchService().exec(batch);
LOGGER.info("GeoFence data updated");
} catch (ServerWebApplicationException ex) {
LOGGER.error("GeoFence error (HTTP:"+ex.getStatus()+"): " + ex.getMessage() , ex);
LOGGER.error("GeoFence data have not been updated");
}
}
}
private static List<RESTBatchOperation> processUserFile(File userFile, UserFileConfig ucfg, Map<String,String> remappedGroupNames) throws IOException {
// load and parse file
List<UserOp> userOps;
try {
UserFileLoader loader = new UserFileLoader(ucfg);
userOps = loader.load(userFile);
UsersProcessor processor = new UsersProcessor();
return processor.buildUserBatchOps(userOps, remappedGroupNames);
} catch (IOException e) {
LOGGER.warn("Error loading file '"+userFile+"': " + e.getMessage(), e);
throw e;
}
}
private static List<RESTBatchOperation> processRuleFile(File ruleFile, RuleFileConfig cfg, Map<String,String> remappedGroupNames) throws IOException {
// load and parse file
List<RuleOp> ruleOps;
try {
RuleFileLoader loader = new RuleFileLoader(cfg);
ruleOps = loader.load(ruleFile);
RulesProcessor processor = new RulesProcessor();
return processor.buildBatchOps(ruleOps, remappedGroupNames, cfg);
} catch (IOException e) {
LOGGER.warn("Error loading file '"+ruleFile+"': " + e.getMessage(), e);
throw e;
}
}
private Map<String, String> retrieveGFGroups(GeoFenceClient client) {
RESTFullUserGroupList groups = client.getUserGroupService().getList(null, null, null);
Map<String, String> ret = new HashMap<String, String>();
for (ShortGroup shortGroup : groups.getList()) {
String groupName = shortGroup.getName();
String old = ret.put(groupName.toUpperCase(), groupName);
if(old != null) {
LOGGER.error("Group name collision in " + old + " and " + groupName);
throw new IllegalStateException("Group name collision ("+groupName.toUpperCase()+")");
}
}
return ret;
}
/**
* Collect all group names from user files.
*
*
* @param cfg
* @param files
* @return a Map having as keys the uppercase name
*/
private Map<String, String> retrieveRequestedGroups(UserFileConfig cfg, List<File> files) {
Map<String, String> upperNames = new HashMap<String, String>();
UserFileLoader loader = new UserFileLoader(cfg);
for (File file : files) {
LOGGER.debug("Collecting group names from file '" + file+"'");
List<UserOp> userOps;
try {
userOps = loader.load(file);
} catch (IOException ex) {
LOGGER.error("Error loading file " + file + ": " + ex.getMessage());
throw new IllegalStateException("Error loading file", ex);
}
for (UserOp userOp : userOps) {
for (String groupName : userOp.getGroups()) {
String old = upperNames.put(groupName.toUpperCase(), groupName);
if(old != null && ! old.equals(groupName)) {
LOGGER.warn("Conflict in group names in files: '"+old+"' and '"+groupName+"'");
}
}
}
}
LOGGER.info("Collected " + upperNames.size() + " groups in " + files.size() + " files");
return upperNames;
}
/**
*
* May add entries to existingGroups
*/
private List<RESTBatchOperation> createGroups(Map<String, String> requestedGroups, Map<String, String> existingGroups) {
List<RESTBatchOperation> ret = new ArrayList<RESTBatchOperation>();
for (String reqUpperGroup : requestedGroups.keySet()) {
if(! existingGroups.containsKey(reqUpperGroup)) {
String realName = requestedGroups.get(reqUpperGroup); // the group real case
LOGGER.warn("Adding new group '"+realName+"'" );
RESTBatchOperation op = RESTBatchOperationFactory.createGroupInputOp(realName);
// RESTBatchOperation op = new RESTBatchOperation();
// op.setService(RESTBatchOperation.ServiceName.groups);
// op.setType(RESTBatchOperation.TypeName.insert);
// RESTInputGroup group = new RESTInputGroup();
// group.setEnabled(Boolean.TRUE);
// group.setName(realName);
// op.setPayload(group);
ret.add(op);
existingGroups.put(reqUpperGroup, realName);
}
}
LOGGER.info("Inserting " + ret.size() + " new groups");
return ret;
}
private GeoFenceClient createClient(GeofenceConfig cfg) {
GeoFenceClient geoFenceClient = new GeoFenceClient();
geoFenceClient.setGeostoreRestUrl(cfg.getRestUrl());
geoFenceClient.setUsername(cfg.getUsername());
geoFenceClient.setPassword(cfg.getPassword());
try {
geoFenceClient.getUserGroupService().count("this_is_a_simple_ping");
return geoFenceClient;
} catch (Exception e) {
LOGGER.error("Can't connect to GeoFence: "+ e.getMessage(), e);
return null;
}
}
private void verifyUsers(RESTBatch batch, GeoFenceClient client) {
for (RESTBatchOperation op : batch.getList()) {
if(op.getService() == RESTBatchOperation.ServiceName.users) {
switch(op.getType()) {
case insert:
{
String userName = ((RESTInputUser)op.getPayload()).getName();
boolean exist = existUser(client, userName);
if(exist)
LOGGER.warn("User " + userName + " already in GeoFence: operation 'insert' is likely to trigger an error.");
}
break;
case update:
case delete:
{
String userName = op.getName();
boolean exist = existUser(client, userName);
if( ! exist )
LOGGER.warn("User " + userName + " not found in GeoFence: operation '"+op.getType()+"' is likely to trigger an error.");
}
break;
}
}
}
}
/**
* Check for rule collision.
* Optionally augment the batch list with rule deletion operations.
*
*/
private void verifyRules(RESTBatch batch, GeoFenceClient client) {
final RuleServiceHelper helper = new RuleServiceHelper(client.getRuleService());
List<RESTBatchOperation> deleteOps = new LinkedList<RESTBatchOperation>();
for (RESTBatchOperation op : batch.getList()) {
if(op.getService() == RESTBatchOperation.ServiceName.rules) {
StringBuilder sb = new StringBuilder();
RESTInputRule rule = (RESTInputRule)op.getPayload();
RuleFilter ruleFilter = new RuleFilter(RuleFilter.SpecialFilterType.DEFAULT);
ruleFilter.setUserGroup(rule.getGroup().getName());
ruleFilter.getUserGroup().setIncludeDefault(false);
sb.append("group:").append(rule.getGroup().getName());
ruleFilter.setLayer(rule.getLayer());
ruleFilter.getLayer().setIncludeDefault(false);
sb.append(" layer:").append(rule.getLayer());
if(rule.getService() != null) {
ruleFilter.setService(rule.getService());
ruleFilter.getService().setIncludeDefault(false);
sb.append(" service:").append(rule.getService());
}
if(rule.getRequest() != null) {
ruleFilter.setRequest(rule.getRequest());
ruleFilter.getRequest().setIncludeDefault(false);
sb.append(" request:").append(rule.getRequest());
}
RESTOutputRuleList rulesFound = helper.get(null, null, false, ruleFilter);
if( ! rulesFound.getList().isEmpty()) {
if(rulesFound.getList().size() == 1) {
if(runInfo.isDeleteObsoleteRules()) {
RESTBatchOperation ruleDelOp = RESTBatchOperationFactory.createDeleteRuleOp(rulesFound.getList().get(0).getId());
deleteOps.add(ruleDelOp);
LOGGER.debug("Replacing rule on " + sb);
} else {
LOGGER.warn("Rule " + rule + " already exists in GeoFence: operation 'insert' is likely to trigger an error.");
for (RESTOutputRule ruleFound : rulesFound) {
LOGGER.info(" - Rule found : " + ruleFound);
}
}
} else if(rulesFound.getList().size() > 1) {
LOGGER.error("Found too many rules matching an input rule");
LOGGER.error("Input Rule is " + rule);
for (RESTOutputRule ruleFound : rulesFound) {
LOGGER.info(" - Rule found : " + ruleFound);
}
throw new IllegalStateException("Found too many rules matching " + rule);
}
}
}
}
if(! deleteOps.isEmpty()) {
LOGGER.info("Adding "+deleteOps.size()+ " rule delete ops at the top of the batch" );
LOGGER.debug("Old list size: "+batch.getList().size() );
batch.getList().addAll(0, deleteOps);
LOGGER.debug("New list size: "+batch.getList().size() );
}
}
private boolean existUser(GeoFenceClient client, String userName) {
try {
client.getUserService().get(userName);
return true;
} catch (ServerWebApplicationException ex) {
if(ex.getStatus() == 404) {
return false;
} else {
LOGGER.error("Error retrieving user '"+userName+"' in GeoFence: " + ex.getMessage() + "("+ex.getStatus()+")");
throw new RuntimeException("Error retrieving user '"+userName+"' in GeoFence", ex);
}
}
}
}