/*
* Copyright (c) 2010-2013 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.evolveum.midpoint.init;
import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration;
import com.evolveum.midpoint.gui.api.util.WebComponentUtil;
import com.evolveum.midpoint.model.api.ModelExecuteOptions;
import com.evolveum.midpoint.model.api.ModelService;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ReportTypeUtil;
import com.evolveum.midpoint.security.api.Authorization;
import com.evolveum.midpoint.security.api.AuthorizationConstants;
import com.evolveum.midpoint.security.api.MidPointPrincipal;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
/**
* @author lazyman
*/
public class InitialDataImport {
private static final Trace LOGGER = TraceManager.getTrace(InitialDataImport.class);
private static final String DOT_CLASS = InitialDataImport.class.getName() + ".";
private static final String OPERATION_INITIAL_OBJECTS_IMPORT = DOT_CLASS + "initialObjectsImport";
private static final String OPERATION_IMPORT_OBJECT = DOT_CLASS + "importObject";
@Autowired
private transient PrismContext prismContext;
private ModelService model;
private TaskManager taskManager;
@Autowired
private MidpointConfiguration configuration;
public void setModel(ModelService model) {
Validate.notNull(model, "Model service must not be null.");
this.model = model;
}
public void setTaskManager(TaskManager taskManager) {
Validate.notNull(taskManager, "Task manager must not be null.");
this.taskManager = taskManager;
}
public void init() throws SchemaException {
LOGGER.info("Starting initial object import (if necessary).");
OperationResult mainResult = new OperationResult(OPERATION_INITIAL_OBJECTS_IMPORT);
Task task = taskManager.createTaskInstance(OPERATION_INITIAL_OBJECTS_IMPORT);
task.setChannel(SchemaConstants.CHANNEL_GUI_INIT_URI);
int count = 0;
int errors = 0;
File[] files = getInitialImportObjects();
LOGGER.debug("Files to be imported: {}.", Arrays.toString(files));
// We need to provide a fake Spring security context here.
// We have to fake it because we do not have anything in the repository yet. And to get
// something to the repository we need a context. Chicken and egg. So we fake the egg.
SecurityContext securityContext = SecurityContextHolder.getContext();
UserType userAdministrator = new UserType();
prismContext.adopt(userAdministrator);
userAdministrator.setName(new PolyStringType(new PolyString("initAdmin", "initAdmin")));
MidPointPrincipal principal = new MidPointPrincipal(userAdministrator);
AuthorizationType superAutzType = new AuthorizationType();
prismContext.adopt(superAutzType, RoleType.class, new ItemPath(RoleType.F_AUTHORIZATION));
superAutzType.getAction().add(AuthorizationConstants.AUTZ_ALL_URL);
Authorization superAutz = new Authorization(superAutzType);
Collection<Authorization> authorities = principal.getAuthorities();
authorities.add(superAutz);
Authentication authentication = new PreAuthenticatedAuthenticationToken(principal, null);
securityContext.setAuthentication(authentication);
for (File file : files) {
try {
LOGGER.debug("Considering initial import of file {}.", file.getName());
PrismObject object = prismContext.parseObject(file);
if (ReportType.class.equals(object.getCompileTimeClass())) {
ReportTypeUtil.applyDefinition(object, prismContext);
}
Boolean importObject = importObject(object, file, task, mainResult);
if (importObject == null) {
continue;
}
if (importObject) {
count++;
} else {
errors++;
}
} catch (Exception ex) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't import file {}", ex, file.getName());
mainResult.recordFatalError("Couldn't import file '" + file.getName() + "'", ex);
}
}
securityContext.setAuthentication(null);
mainResult.recomputeStatus("Couldn't import objects.");
LOGGER.info("Initial object import finished ({} objects imported, {} errors)", count, errors);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Initialization status:\n" + mainResult.debugDump());
}
}
/**
* @param object
* @param task
* @param mainResult
* @return null if nothing was imported, true if it was success, otherwise false
*/
private Boolean importObject(PrismObject object, File file, Task task, OperationResult mainResult) {
OperationResult result = mainResult.createSubresult(OPERATION_IMPORT_OBJECT);
boolean importObject = true;
try {
model.getObject(object.getCompileTimeClass(), object.getOid(), SelectorOptions.createCollection(GetOperationOptions.createAllowNotFound()), task, result);
importObject = false;
result.recordSuccess();
} catch (ObjectNotFoundException ex) {
importObject = true;
} catch (Exception ex) {
if (!importObject){
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't get object with oid {} from model", ex,
object.getOid());
result.recordWarning("Couldn't get object with oid '" + object.getOid() + "' from model",
ex);
}
}
if (!importObject) {
return null;
}
ObjectDelta delta = ObjectDelta.createAddDelta(object);
try {
LOGGER.info("Starting initial import of file {}.", file.getName());
model.executeChanges(WebComponentUtil.createDeltaCollection(delta), ModelExecuteOptions.createIsImport(), task, result);
result.recordSuccess();
LOGGER.info("Created {} as part of initial import", object);
return true;
} catch (Exception e) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't import {} from file {}: ", e, object,
file.getName(), e.getMessage());
result.recordFatalError(e);
LOGGER.info("\n" + result.debugDump());
return false;
}
}
private File getResource(String name) {
URI path;
try {
LOGGER.trace("getResource: name = {}", name);
path = InitialDataImport.class.getClassLoader().getResource(name).toURI();
LOGGER.trace("getResource: path = {}", path);
//String updatedPath = path.toString().replace("zip:/", "jar:/");
//LOGGER.trace("getResource: path updated = {}", updatedPath);
//path = new URI(updatedPath);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("parameter name = " + name, e);
}
return new File(path);
}
private File[] getInitialImportObjects() {
URL path = InitialDataImport.class.getClassLoader().getResource("initial-objects");
String resourceType = path.getProtocol();
File[] files = null;
File folder = null;
if ("zip".equals(resourceType) || "jar".equals(resourceType)) {
try {
File tmpDir = new File(configuration.getMidpointHome()+"/tmp");
if (!tmpDir.mkdir()) {
LOGGER.warn("Failed to create temporary directory for initial objects {}. Maybe it already exists", configuration.getMidpointHome()+"/tmp");
}
tmpDir = new File(configuration.getMidpointHome()+"/tmp/initial-objects");
if (!tmpDir.mkdir()) {
LOGGER.warn("Failed to create temporary directory for initial objects {}. Maybe it already exists", configuration.getMidpointHome()+"/tmp/initial-objects");
}
//prerequisite: we are expecting that the files are store in the same archive as the source code that is loading it
URI src = InitialDataImport.class.getProtectionDomain().getCodeSource().getLocation().toURI();
LOGGER.trace("InitialDataImport code location: {}", src);
Map<String, String> env = new HashMap<>();
env.put("create", "false");
URI normalizedSrc = new URI(src.toString().replaceFirst("file:", "jar:file:"));
LOGGER.trace("InitialDataImport normalized code location: {}", normalizedSrc);
try (FileSystem zipfs = FileSystems.newFileSystem(normalizedSrc, env)) {
Path pathInZipfile = zipfs.getPath("/initial-objects");
//TODO: use some well defined directory, e.g. midPoint home
final Path destDir = Paths.get(configuration.getMidpointHome()+"/tmp");
Files.walkFileTree(pathInZipfile, new SimpleFileVisitor<Path>(){
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs) throws IOException {
final Path destFile = Paths.get(destDir.toString(),
file.toString());
LOGGER.trace("Extracting file {} to {}", file, destFile);
Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException {
final Path dirToCreate = Paths.get(destDir.toString(),
dir.toString());
if(Files.notExists(dirToCreate)){
LOGGER.trace("Creating directory {}", dirToCreate);
Files.createDirectory(dirToCreate);
}
return FileVisitResult.CONTINUE;
}
});
}
folder = new File(configuration.getMidpointHome()+"/tmp/initial-objects");
} catch (IOException ex) {
throw new RuntimeException("Failed to copy initial objects file out of the archive to the temporary directory", ex);
} catch (URISyntaxException ex) {
throw new RuntimeException("Failed get URI for the source code bundled with initial objects", ex);
}
}
if ("file".equals(resourceType)) {
folder = getResource("initial-objects");
}
files = folder.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.isDirectory()) {
return false;
}
return true;
}
});
Arrays.sort(files, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
int n1 = getNumberFromName(o1);
int n2 = getNumberFromName(o2);
return n1 - n2;
}
});
return files;
}
private int getNumberFromName(File file) {
String name = file.getName();
String number = StringUtils.left(name, 3);
if (number.matches("[\\d]+")) {
return Integer.parseInt(number);
}
return 0;
}
}