Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.appng.api.support.ApplicationConfigProviderImpl Maven / Gradle / Ivy
/*
* Copyright 2011-2021 the original author or authors.
*
* 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 org.appng.api.support;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.bind.JAXBException;
import org.apache.commons.lang3.ArrayUtils;
import org.appng.api.ApplicationConfigProvider;
import org.appng.api.InvalidConfigurationException;
import org.appng.api.model.Resource;
import org.appng.api.model.ResourceType;
import org.appng.api.model.Resources;
import org.appng.xml.MarshallService;
import org.appng.xml.application.ApplicationInfo;
import org.appng.xml.platform.Action;
import org.appng.xml.platform.ApplicationRootConfig;
import org.appng.xml.platform.Datasource;
import org.appng.xml.platform.Datasources;
import org.appng.xml.platform.Event;
import org.appng.xml.platform.Events;
import org.appng.xml.platform.FieldDef;
import org.appng.xml.platform.MetaData;
import org.appng.xml.platform.PageDefinition;
import org.appng.xml.platform.Pages;
import org.appng.xml.platform.Param;
import org.appng.xml.platform.Platform;
import lombok.extern.slf4j.Slf4j;
/**
* Default {@link ApplicationConfigProvider}-implementation.
*
* @author Matthias Müller
*/
@Slf4j
public class ApplicationConfigProviderImpl implements ApplicationConfigProvider {
private static final String RESOURCE_MAP_KEY_EVENT = "event:";
private static final String RESOURCE_MAP_KEY_PAGE = "page:";
private static final String RESOURCE_MAP_KEY_DATASOURCE = "datasource:";
private static final String RESOURCE_MAP_KEY_APPLICATION_ROOT_CONFIG = "applicationRootConfig";
protected ActionMap actionMap;
protected DataSourceMap datasourceMap;
protected PageMap pageMap;
protected EventMap eventMap;
protected Map resourceMap;
protected List descendantDatasources = new ArrayList<>();
protected ConfigValidator validator;
protected ApplicationRootConfig rootConfig;
private Set sessionParams;
private Collection xmlFiles;
private String defaultPage;
private boolean devMode;
private boolean loaded = false;
private byte[] data;
private String applicationName;
private ApplicationInfo applicationInfo;
private Resources resources;
private ApplicationConfigProviderImpl(MarshallService marshallService, String applicationName,
Collection applicationResources, ApplicationInfo applicationInfo, Resources resources,
Boolean devMode) throws InvalidConfigurationException {
this.xmlFiles = applicationResources;
this.applicationName = applicationName;
this.applicationInfo = applicationInfo;
this.validator = new ConfigValidator(this);
this.resourceMap = new HashMap<>();
this.resources = resources;
setDevMode(devMode);
loadConfig(marshallService);
}
private ApplicationConfigProviderImpl(String applicationName, boolean devMode, byte[] data)
throws IOException, ClassNotFoundException {
this.applicationName = applicationName;
this.data = ArrayUtils.clone(data);
this.devMode = devMode;
this.loaded = true;
readData();
}
public ApplicationConfigProviderImpl(MarshallService marshallService, String applicationName,
Resources applicationResources, boolean devMode) throws InvalidConfigurationException {
this.resources = applicationResources;
this.xmlFiles = applicationResources.getResources(ResourceType.XML);
this.applicationName = applicationName;
this.applicationInfo = applicationResources.getApplicationInfo();
this.validator = new ConfigValidator(this);
this.resourceMap = new HashMap<>();
setDevMode(devMode);
loadConfig(marshallService);
}
private void setDevMode(boolean devMode) {
this.devMode = devMode;
}
public Map getDataSources() {
return datasourceMap;
}
private synchronized void loadConfig(MarshallService marshallService) throws InvalidConfigurationException {
long start = System.currentTimeMillis();
try {
clear();
if (!loaded || devMode) {
readResources(marshallService);
} else {
readData();
}
} catch (Exception e) {
LOGGER.error("error while reading configuration", e);
}
long duration = System.currentTimeMillis() - start;
LOGGER.debug("loading config for application {} took {}ms", applicationName, duration);
}
private void readResources(MarshallService marshallService) throws IOException, InvalidConfigurationException {
for (Resource applicationResource : xmlFiles) {
String name = applicationResource.getName();
InputStream inputStream = null;
try {
if (devMode) {
inputStream = new FileInputStream(applicationResource.getCachedFile());
} else {
inputStream = new ByteArrayInputStream(applicationResource.getBytes());
}
Object object = marshallService.unmarshall(inputStream);
readConfig(name, object);
} catch (JAXBException e) {
LOGGER.error(String.format("error while unmarshalling %s", name), e);
} finally {
if (null != inputStream) {
inputStream.close();
}
}
}
processInheritance(marshallService);
writeData();
if (devMode) {
validate();
}
loaded = true;
}
private void validate() throws MalformedURLException, InvalidConfigurationException, IOException {
URLClassLoader classLoader;
Set jars = resources.getResources(ResourceType.JAR);
URL[] urls = new URL[jars.size()];
int i = 0;
for (Resource resource : jars) {
urls[i++] = resource.getCachedFile().toURI().toURL();
}
classLoader = new URLClassLoader(urls, getClass().getClassLoader());
try {
validator.validate(applicationName, classLoader);
} finally {
if (null != classLoader) {
classLoader.close();
classLoader = null;
}
}
}
private void processInheritance(MarshallService marshallService) {
// we have to face the fact that there could be a chain of inheritance. We have to loop over the list of
// descendants until all have been processed
while (!descendantDatasources.isEmpty()) {
int actualSize = descendantDatasources.size();
List descendantsWithoutAncestor = new ArrayList<>();
for (Object[] descendantDef : descendantDatasources) {
Datasource ds = (Datasource) descendantDef[0];
Datasource ancestor = getDatasource(DatasourceInheritanceHelper.getAncestorId(ds.getId()));
if (null != ancestor) {
Datasource descendant = DatasourceInheritanceHelper.inherit(ds, ancestor, marshallService);
if (null != descendant) {
addDataSource(descendant, (String) descendantDef[1]);
} else {
LOGGER.error("inheritance did not create an new datasource instance");
}
} else {
// This can happen if the ancestor also inherits from another datasource. As long as this
// inheritance is not processed, the ancestor cannot be found by its final id
descendantsWithoutAncestor.add(descendantDef);
}
}
// reinitialize the list with the left over descendants where the inheritance could not be processed yet.
descendantDatasources = descendantsWithoutAncestor;
// the size must decrease on each iteration if not, we have an descendant where the inheritance can not be
// processed (maybe because of a typo). Thus the abort condition for the main loop will never be fulfilled
// (the list will never be empty) so we have to handle this case explicit and write some errors in the
// log-file
if (actualSize <= descendantDatasources.size()) {
for (Object[] ds : descendantDatasources) {
LOGGER.error("Cannot process inheritance for {} maybe the ancestor is not defined.",
((Datasource) ds[0]).getId());
}
descendantDatasources.clear();
return;
}
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void readData() throws IOException, ClassNotFoundException {
Object o = null;
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream is = new ObjectInputStream(bais)) {
while ((o = is.readObject()) != null) {
if (o instanceof PageMap) {
this.pageMap = (PageMap) o;
} else if (o instanceof ActionMap) {
this.actionMap = (ActionMap) o;
} else if (o instanceof DataSourceMap) {
this.datasourceMap = (DataSourceMap) o;
} else if (o instanceof EventMap) {
this.eventMap = (EventMap) o;
} else if (o instanceof ApplicationRootConfig) {
this.rootConfig = (ApplicationRootConfig) o;
} else if (o instanceof String) {
this.defaultPage = (String) o;
} else if (o instanceof Set) {
this.sessionParams = (Set) o;
} else if (o instanceof Map) {
this.resourceMap = (Map) o;
}
}
} catch (EOFException e) {
// ObjectInputStream seems to work like this...
} catch (IOException e) {
throw e;
}
}
private void writeData() throws IOException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(out)) {
data = null;
outputStream.writeObject(pageMap);
outputStream.writeObject(actionMap);
outputStream.writeObject(datasourceMap);
outputStream.writeObject(eventMap);
outputStream.writeObject(rootConfig);
if (null != defaultPage) {
outputStream.writeObject(defaultPage);
}
if (null != sessionParams) {
outputStream.writeObject(sessionParams);
}
out.close();
outputStream.close();
data = out.toByteArray();
LOGGER.debug("wrote {} bytes of data for application {}", data.length, applicationName);
} catch (IOException e) {
throw e;
}
}
private void addDataSource(Datasource ds, String resourceName) {
Datasource oldVal = datasourceMap.put(ds.getId(), ds);
resourceMap.put(RESOURCE_MAP_KEY_DATASOURCE + ds.getId(), resourceName);
if (null != oldVal) {
LOGGER.warn("overriding previously defined datasource '{}'", ds.getId());
}
LOGGER.trace("added datasource '{}'", ds.getId());
}
// there should be only one application root config. Therefore no ID is needed.
private void addApplicationRootConfig(String resourceName) {
resourceMap.put(RESOURCE_MAP_KEY_APPLICATION_ROOT_CONFIG, resourceName);
}
private void addEvent(Event e, String resourceName) {
Event oldVal = eventMap.put(e.getId(), e);
resourceMap.put(RESOURCE_MAP_KEY_EVENT + e.getId(), resourceName);
Map actions = new HashMap<>();
for (Action action : e.getActions()) {
actions.put(action.getId(), action);
}
actionMap.put(e.getId(), actions);
if (null != oldVal) {
LOGGER.warn("overriding previously defined event '{}'", e.getId());
}
LOGGER.trace("added event '{}'", e.getId());
}
private void addPage(PageDefinition p, String resourceName) {
PageDefinition oldVal = pageMap.put(p.getId(), p);
resourceMap.put(RESOURCE_MAP_KEY_PAGE + p.getId(), resourceName);
if (null != oldVal) {
LOGGER.warn("overriding previously defined page '{}'", p.getId());
}
if ("index".equals(p.getType())) {
this.defaultPage = p.getId();
LOGGER.trace("added default page '{}'", p.getId());
} else {
LOGGER.trace("added page '{}'", p.getId());
}
}
protected synchronized void clear() {
this.sessionParams = new HashSet<>();
this.pageMap = new PageMap();
this.datasourceMap = new DataSourceMap();
this.eventMap = new EventMap();
this.actionMap = new ActionMap();
this.defaultPage = null;
this.rootConfig = null;
}
public Action getAction(String eventId, String actionId) {
String realId;
if (actionId.startsWith(eventId + ":")) {
realId = actionId.substring((eventId + ":").length() + 1);
} else {
realId = actionId;
}
Map actions = getActions(eventId);
if (null != actions) {
return actions.get(realId);
}
return null;
}
public Map getActions(String eventId) {
return actionMap.get(eventId);
}
public Datasource getDatasource(String id) {
Datasource datasource = datasourceMap.get(id);
initMetaData(datasource);
return datasource;
}
private void initMetaData(Datasource datasource) {
if (null != datasource) {
MetaData metaData = datasource.getConfig().getMetaData();
setBindings(metaData.getBinding(), metaData.getFields());
}
}
private void setBindings(String binding, List fields) {
for (FieldDef fieldDef : fields) {
if (null == fieldDef.getBinding()) {
if (binding != null) {
fieldDef.setBinding(binding + "." + fieldDef.getName());
} else {
fieldDef.setBinding(fieldDef.getName());
}
}
setBindings(fieldDef.getBinding(), fieldDef.getFields());
}
}
public String getDefaultPage() {
return defaultPage;
}
public Event getEvent(String id) {
return eventMap.get(id);
}
public Set getEventIds() {
return eventMap.keySet();
}
public PageDefinition getPage(String id) {
return pageMap.get(id);
}
public Map getPages() {
return pageMap;
}
public ApplicationRootConfig getApplicationRootConfig() {
return rootConfig;
}
public Set getSessionParams() {
return sessionParams;
}
private boolean readConfig(String resourceName, Object object) throws InvalidConfigurationException {
LOGGER.trace("reading {}, found {}", resourceName, object.getClass().getSimpleName());
if (object instanceof ApplicationRootConfig) {
if (null != rootConfig) {
throw new InvalidConfigurationException(applicationName,
"found second ApplicationRootConfig, this is a configuration error!");
}
rootConfig = (ApplicationRootConfig) object;
List sessionParam = rootConfig.getConfig().getSession().getSessionParams().getSessionParam();
for (Param param : sessionParam) {
this.sessionParams.add(param.getName());
}
Pages pages = rootConfig.getPages();
if (null != pages) {
for (PageDefinition page : pages.getPageList()) {
addPage(page, resourceName);
}
}
addApplicationRootConfig(resourceName);
} else if (object instanceof Pages) {
Pages pages = (Pages) object;
List pageList = pages.getPageList();
for (PageDefinition page : pageList) {
addPage(page, resourceName);
}
} else if (object instanceof PageDefinition) {
PageDefinition page = (PageDefinition) object;
addPage(page, resourceName);
} else if (object instanceof Datasource) {
Datasource ds = (Datasource) object;
addDataSource(ds, resourceName);
} else if (object instanceof Datasources) {
Datasources sources = (Datasources) object;
for (Datasource ds : sources.getDatasourceList()) {
if (DatasourceInheritanceHelper.isInheriting(ds.getId())) {
this.descendantDatasources.add(new Object[] { ds, resourceName });
} else {
addDataSource(ds, resourceName);
}
}
} else if (object instanceof Event) {
Event ev = (Event) object;
addEvent(ev, resourceName);
} else if (object instanceof Events) {
Events events = (Events) object;
for (Event ev : events.getEventList()) {
addEvent(ev, resourceName);
}
} else if (object instanceof Platform) {
return true;
} else {
LOGGER.error("ignoring unsupported type: {}", object.getClass());
}
return false;
}
public void setDefaultPage(String defaultPage) {
this.defaultPage = defaultPage;
}
public void setSessionParams(Set sessionParams) {
this.sessionParams = sessionParams;
}
public String getResourceNameForEvent(String eventId) {
return resourceMap.get(RESOURCE_MAP_KEY_EVENT + eventId);
}
public String getResourceNameForPage(String pageId) {
return resourceMap.get(RESOURCE_MAP_KEY_PAGE + pageId);
}
public String getResourceNameForDataSource(String datasourceId) {
return resourceMap.get(RESOURCE_MAP_KEY_DATASOURCE + datasourceId);
}
public String getResourceNameForApplicationRootConfig() {
return resourceMap.get(RESOURCE_MAP_KEY_APPLICATION_ROOT_CONFIG);
}
static class PageMap extends HashMap {
}
static class ActionMap extends HashMap> {
}
static class DataSourceMap extends HashMap {
}
static class EventMap extends HashMap {
}
public synchronized ApplicationConfigProvider cloneConfig(MarshallService marshallService)
throws InvalidConfigurationException {
try {
ApplicationConfigProvider configProvider = null;
if (devMode) {
configProvider = new ApplicationConfigProviderImpl(marshallService, applicationName, xmlFiles,
applicationInfo, resources, devMode);
} else {
configProvider = new ApplicationConfigProviderImpl(applicationName, devMode, ArrayUtils.clone(data));
}
return configProvider;
} catch (Exception e) {
throw new InvalidConfigurationException(applicationName, "error while reading data", e);
}
}
public ApplicationInfo getApplicationInfo() {
return applicationInfo;
}
public Resources getResources() {
return resources;
}
public void close() throws IOException {
for (Resource applicationResource : xmlFiles) {
((Closeable) applicationResource).close();
}
actionMap.clear();
eventMap.clear();
datasourceMap.clear();
pageMap.clear();
resourceMap.clear();
actionMap = null;
eventMap = null;
datasourceMap = null;
pageMap = null;
data = null;
resourceMap = null;
xmlFiles = null;
if (null != resources) {
resources.close();
resources = null;
}
}
}