/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.isis.core.metamodel.services.grid;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.PostConstruct;
import javax.xml.bind.JAXBContext;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Maps;
import com.google.common.io.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.isis.applib.DomainObjectContainer;
import org.apache.isis.applib.annotation.DomainService;
import org.apache.isis.applib.annotation.NatureOfService;
import org.apache.isis.applib.annotation.Programmatic;
import org.apache.isis.applib.layout.component.Grid;
import org.apache.isis.applib.services.grid.GridLoaderService;
import org.apache.isis.applib.services.grid.GridSystemService;
import org.apache.isis.applib.services.jaxb.JaxbService;
import org.apache.isis.core.metamodel.deployment.DeploymentCategoryProvider;
@DomainService(
nature = NatureOfService.DOMAIN,
menuOrder = "" + Integer.MAX_VALUE
)
public class GridLoaderServiceDefault implements GridLoaderService {
private static final Logger LOG = LoggerFactory.getLogger(GridLoaderServiceDefault.class);
// for better logging messages (used only in prototyping mode)
private final Map<Class<?>, String> badXmlByClass = Maps.newHashMap();
// cache (used only in prototyping mode)
private final Map<String, Grid> gridByXml = Maps.newHashMap();
private List<Class<? extends Grid>> pageImplementations;
@PostConstruct
public void init(){
pageImplementations = FluentIterable.from(gridSystemServices)
.transform(new Function<GridSystemService, Class<? extends Grid>>() {
@Override
public Class<? extends Grid> apply(final GridSystemService gridSystemService) {
return gridSystemService.gridImplementation();
}
})
.toList();
}
@Override
public boolean supportsReloading() {
return !deploymentCategoryProvider.getDeploymentCategory().isProduction();
}
@Override
public void remove(final Class<?> domainClass) {
if(!supportsReloading()) {
return;
}
badXmlByClass.remove(domainClass);
final String xml = loadXml(domainClass);
if(xml == null) {
return;
}
gridByXml.remove(xml);
}
@Override
@Programmatic
public boolean existsFor(final Class<?> domainClass) {
final URL resource = Resources.getResource(domainClass, resourceNameFor(domainClass));
return resource != null;
}
@Override
@Programmatic
public Grid load(final Class<?> domainClass) {
final String xml = loadXml(domainClass);
if(xml == null) {
return null;
}
if(supportsReloading()) {
final Grid grid = gridByXml.get(xml);
if(grid != null) {
return grid;
}
final String badXml = badXmlByClass.get(domainClass);
if(badXml != null) {
if(Objects.equals(xml, badXml)) {
// seen this before and already logged; just quit
return null;
} else {
// this different XML might be good
badXmlByClass.remove(domainClass);
}
}
}
try {
// all known implementations of Page
final JAXBContext context = JAXBContext.newInstance(pageImplementations.toArray(new Class[0]));
final Grid grid = (Grid) jaxbService.fromXml(context, xml);
grid.setDomainClass(domainClass);
if(supportsReloading()) {
gridByXml.put(xml, grid);
}
return grid;
} catch(Exception ex) {
if(supportsReloading()) {
// save fact that this was bad XML, so that we don't log again if called next time
badXmlByClass.put(domainClass, xml);
}
// note that we don't blacklist if the file exists but couldn't be parsed;
// the developer might fix so we will want to retry.
final String resourceName = resourceNameFor(domainClass);
final String message = "Failed to parse " + resourceName + " file (" + ex.getMessage() + ")";
if(supportsReloading()) {
container.warnUser(message);
}
LOG.warn(message);
return null;
}
}
private String loadXml(final Class<?> domainClass) {
final String resourceName = resourceNameFor(domainClass);
try {
return resourceContentOf(domainClass, resourceName);
} catch (IOException | IllegalArgumentException ex) {
if(LOG.isDebugEnabled()) {
final String message = String .format(
"Failed to locate file %s (relative to %s.class); ex: %s)",
resourceName, domainClass.getName(), ex.getMessage());
LOG.debug(message);
}
return null;
}
}
private static String resourceContentOf(final Class<?> cls, final String resourceName) throws IOException {
final URL url = Resources.getResource(cls, resourceName);
return Resources.toString(url, Charset.defaultCharset());
}
private String resourceNameFor(final Class<?> domainClass) {
return domainClass.getSimpleName() + ".layout.xml";
}
//region > injected dependencies
@javax.inject.Inject
DeploymentCategoryProvider deploymentCategoryProvider;
@javax.inject.Inject
DomainObjectContainer container;
@javax.inject.Inject
JaxbService jaxbService;
@javax.inject.Inject
List<GridSystemService> gridSystemServices;
//endregion
}