| /******************************************************************************** |
| * Copyright (c) 2015-2019 Contributors to the Eclipse Foundation |
| * |
| * See the NOTICE file(s) distributed with this work for additional |
| * information regarding copyright ownership. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| ********************************************************************************/ |
| package org.eclipse.mdm.apicopy.boundary; |
| |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.stream.Collectors; |
| |
| import javax.ejb.Stateless; |
| import javax.inject.Inject; |
| |
| import org.eclipse.mdm.api.base.ConnectionException; |
| import org.eclipse.mdm.api.base.ServiceNotProvidedException; |
| import org.eclipse.mdm.api.base.model.Channel; |
| import org.eclipse.mdm.api.base.model.ChannelGroup; |
| import org.eclipse.mdm.api.base.model.Entity; |
| import org.eclipse.mdm.api.base.model.Measurement; |
| import org.eclipse.mdm.api.base.model.Test; |
| import org.eclipse.mdm.api.base.model.TestStep; |
| import org.eclipse.mdm.api.base.query.DataAccessException; |
| import org.eclipse.mdm.api.dflt.ApplicationContext; |
| import org.eclipse.mdm.api.dflt.ApplicationContextFactory; |
| import org.eclipse.mdm.api.dflt.EntityManager; |
| import org.eclipse.mdm.api.dflt.model.Pool; |
| import org.eclipse.mdm.api.dflt.model.Project; |
| import org.eclipse.mdm.apicopy.control.ApiCopyException; |
| import org.eclipse.mdm.apicopy.control.ExportTask; |
| import org.eclipse.mdm.connector.boundary.ConnectorService; |
| import org.eclipse.mdm.shoppingbasket.entity.MDMItem; |
| import org.eclipse.mdm.shoppingbasket.entity.ShoppingBasket; |
| |
| import com.google.common.collect.BiMap; |
| import com.google.common.collect.ImmutableBiMap; |
| import com.google.common.collect.ImmutableMap; |
| |
| /** |
| * Service for importing an ATFX file into a MDM datasource. |
| * |
| */ |
| @Stateless |
| public class ExportService { |
| |
| // TODO make temp path configurable |
| private static final Path TMP = Paths.get(System.getProperty("java.io.tmpdir")); |
| |
| @Inject |
| private ConnectorService connectorService; |
| |
| private final String atfxContextFactoryClassname = "org.eclipse.mdm.api.atfxadapter.ATFXContextFactory"; |
| |
| /** |
| * Default public constructor |
| */ |
| public ExportService() { |
| } |
| |
| /** |
| * Imports an ATFX file into the provided datasource. |
| * |
| * @param atfxFileSupplier returns a {@link Path} to an ATFX file. Accompanying |
| * binary files are expected to be on the same folder. |
| * @return |
| */ |
| public Path exportAtfx(ShoppingBasket basket) { |
| |
| try { |
| Path tmpDir = Files.createTempDirectory(TMP, "atfxexport"); |
| Path atfxFile = tmpDir.resolve("export.atfx"); |
| writeToFile(atfxFile, ExportTask.class.getResourceAsStream("/emptyAtfx.xml")); |
| |
| Map<String, String> params = ImmutableMap.of("atfxfile", atfxFile.toFile().getAbsolutePath(), |
| "freetext.active", "false", "includeCatalog", "true"); |
| ApplicationContext contextDst = connectContext(atfxContextFactoryClassname, params); |
| |
| Map<String, List<MDMItem>> map = basket.getItems().stream().map(i -> i.getRestURI().getPath()) |
| .map(p -> split(p)).collect(Collectors.groupingBy(p -> p.getSource())); |
| if (map.size() != 1) { |
| throw new ApiCopyException("Currently only exports from exactly one application context are allowed!"); |
| } |
| String srcContextName = map.keySet().iterator().next(); |
| |
| ApplicationContext contextSrc = connectorService.getContextByName(srcContextName); |
| List<? extends Entity> entities = loadEntities(contextSrc, basket); |
| |
| /* |
| * Copy the catalog (which involves creating the ContextComponts). |
| */ |
| ExportTask task = new ExportTask(contextSrc, contextDst); |
| task.copyCatalog(); |
| contextDst.close(); |
| |
| /* |
| * After creating the ContextComponents, the application model has to be |
| * reloaded. This is normally done in a co-session, but openATFX does not |
| * support co-sessions. Thus we have to create a new session by creating a new |
| * application context. |
| */ |
| contextDst = connectContext(atfxContextFactoryClassname, params); |
| task = new ExportTask(contextSrc, contextDst); |
| task.copy(entities); |
| contextDst.close(); |
| |
| return tmpDir; |
| } catch (ServiceNotProvidedException e) { |
| throw new ApiCopyException("Could not retrive all required services from ATFX application context!", e); |
| } catch (ConnectionException e) { |
| throw new ApiCopyException("Could not create ATFX application context!", e); |
| } catch (IOException e) { |
| throw new ApiCopyException("Cold not create temporary directory!", e); |
| } |
| } |
| |
| private List<? extends Entity> loadEntities(ApplicationContext contextSrc, ShoppingBasket basket) |
| throws ServiceNotProvidedException, DataAccessException { |
| EntityManager em = contextSrc.getEntityManager() |
| .orElseThrow(() -> new ServiceNotProvidedException(EntityManager.class)); |
| |
| return basket.getItems().stream().map(i -> i.getRestURI().getPath()).map(p -> split(p)) |
| .map(i -> em.load(typeToClass(i.getType()), i.getId())).collect(Collectors.toList()); |
| } |
| |
| private Class<? extends Entity> typeToClass(String s) { |
| return ENTITY2FRAGMENT_URI.inverse().get(s); |
| } |
| |
| private static final BiMap<Class<? extends Entity>, String> ENTITY2FRAGMENT_URI = ImmutableBiMap |
| .<Class<? extends Entity>, String>builder().put(Project.class, "projects").put(Pool.class, "pools") |
| .put(Test.class, "tests").put(TestStep.class, "teststeps").put(Measurement.class, "measurements") |
| .put(ChannelGroup.class, "channelgroups").put(Channel.class, "channels").build(); |
| |
| private MDMItem split(String path) { |
| |
| String[] splitted = path.split("/"); |
| MDMItem item = new MDMItem(); |
| item.setId(splitted[splitted.length - 1]); |
| item.setType(splitted[splitted.length - 2]); |
| item.setSource(splitted[splitted.length - 3]); |
| return item; |
| } |
| |
| /** |
| * Connects to a {@link ApplicationContext}. |
| * |
| * @param contextFactoryClassname classname of the |
| * {@link ApplicationContextFactory} |
| * @param parameters connection parameters |
| * @return connected {@link ApplicationContext} |
| * @throws ConnectionException |
| */ |
| private ApplicationContext connectContext(String contextFactoryClassname, Map<String, String> parameters) |
| throws ConnectionException { |
| try { |
| |
| Class<? extends ApplicationContextFactory> contextFactoryClass = Thread.currentThread() |
| .getContextClassLoader().loadClass(contextFactoryClassname) |
| .asSubclass(ApplicationContextFactory.class); |
| |
| ApplicationContextFactory contextFactory = contextFactoryClass.newInstance(); |
| return contextFactory.connect(parameters); |
| } catch (Exception e) { |
| throw new ConnectionException( |
| "failed to initialize entity manager using factory '" + contextFactoryClassname + "'", e); |
| } |
| } |
| |
| private static void writeToFile(Path resolve, InputStream entityAs) throws IOException { |
| try (OutputStream out = new FileOutputStream(resolve.toFile())) { |
| int read = 0; |
| byte[] bytes = new byte[1024]; |
| |
| while ((read = entityAs.read(bytes)) != -1) { |
| out.write(bytes, 0, read); |
| } |
| } |
| } |
| } |