| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package org.geoserver.featurestemplating.builders.visitors; | ||
|
|
||
| import org.geoserver.featurestemplating.builders.AbstractTemplateBuilder; | ||
| import org.geoserver.featurestemplating.builders.SourceBuilder; | ||
| import org.geoserver.featurestemplating.builders.impl.CompositeBuilder; | ||
| import org.geoserver.featurestemplating.builders.impl.DynamicValueBuilder; | ||
| import org.geoserver.featurestemplating.builders.impl.IteratingBuilder; | ||
| import org.geoserver.featurestemplating.builders.impl.RootBuilder; | ||
| import org.geoserver.featurestemplating.builders.impl.StaticBuilder; | ||
|
|
||
| /** | ||
| * Visitor with {@code visit} methods to be called by {@link | ||
| * org.geoserver.featurestemplating.builders.TemplateBuilder#accept} | ||
| */ | ||
| public interface TemplateVisitor { | ||
|
|
||
| /** | ||
| * Used to visit a {@link RootBuilder}. | ||
| * | ||
| * @param rootBuilder the root builder. | ||
| * @param extradata | ||
| * @return the eventual result of the visiting process. | ||
| */ | ||
| Object visit(RootBuilder rootBuilder, Object extradata); | ||
|
|
||
| /** | ||
| * Used to visit a {@link IteratingBuilder}. | ||
| * | ||
| * @param iteratingBuilder the iterating builder to be visited. | ||
| * @param extradata | ||
| * @return the eventual result of the visiting process. | ||
| */ | ||
| Object visit(IteratingBuilder iteratingBuilder, Object extradata); | ||
|
|
||
| /** | ||
| * Used to visit a {@link CompositeBuilder}. | ||
| * | ||
| * @param compositeBuilder the composite builder to be visited. | ||
| * @param extradata | ||
| * @return the eventual result of the visiting process. | ||
| */ | ||
| Object visit(CompositeBuilder compositeBuilder, Object extradata); | ||
|
|
||
| /** | ||
| * Used to visit a {@link DynamicValueBuilder}. | ||
| * | ||
| * @param dynamicBuilder the dynamic builder to be visited. | ||
| * @param extradata | ||
| * @return the eventual result of the visiting process. | ||
| */ | ||
| Object visit(DynamicValueBuilder dynamicBuilder, Object extradata); | ||
|
|
||
| /** | ||
| * Used to visit a {@link StaticBuilder}. | ||
| * | ||
| * @param staticBuilder the static builder to be visited. | ||
| * @param extradata | ||
| * @return the eventual result of the visiting process. | ||
| */ | ||
| Object visit(StaticBuilder staticBuilder, Object extradata); | ||
|
|
||
| /** | ||
| * Used to visit a {@link SourceBuilder}. | ||
| * | ||
| * @param sourceBuilder the source builder to be visited. | ||
| * @param extradata | ||
| * @return the eventual result of the visiting process. | ||
| */ | ||
| Object visit(SourceBuilder sourceBuilder, Object extradata); | ||
|
|
||
| /** | ||
| * Used to visit a {@link AbstractTemplateBuilder} | ||
| * | ||
| * @param abstractTemplateBuilder the abstract builder to be visited. | ||
| * @param extradata | ||
| * @return the eventual result of the visiting process. | ||
| */ | ||
| Object visit(AbstractTemplateBuilder abstractTemplateBuilder, Object extradata); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| import java.util.Optional; | ||
| import java.util.Set; | ||
| import org.geoserver.catalog.Catalog; | ||
| import org.geoserver.catalog.FeatureTypeInfo; | ||
| import org.geoserver.platform.GeoServerExtensions; | ||
|
|
||
| /** | ||
| * A Template event listener that handle Template rules update when a TemplateInfo is deleted or | ||
| * modified. | ||
| */ | ||
| public class FeatureTypeTemplateDAOListener implements TemplateDAOListener { | ||
|
|
||
| private FeatureTypeInfo fti; | ||
|
|
||
| public FeatureTypeTemplateDAOListener(FeatureTypeInfo featureTypeInfo) { | ||
| this.fti = featureTypeInfo; | ||
| } | ||
|
|
||
| @Override | ||
| public void handleDeleteEvent(TemplateInfoEvent deleteEvent) { | ||
| TemplateLayerConfig layerConfig = | ||
| fti.getMetadata().get(TemplateLayerConfig.METADATA_KEY, TemplateLayerConfig.class); | ||
| TemplateInfo ti = deleteEvent.getSource(); | ||
| if (layerConfig != null) { | ||
| Set<TemplateRule> rules = layerConfig.getTemplateRules(); | ||
| if (!rules.isEmpty()) { | ||
| if (rules.removeIf( | ||
| r -> | ||
| r.getTemplateIdentifier() | ||
| .equals(deleteEvent.getSource().getIdentifier()))) { | ||
| fti.getMetadata().put(TemplateLayerConfig.METADATA_KEY, layerConfig); | ||
| saveFeatureTypeInfo(); | ||
| updateCache(ti); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void handleUpdateEvent(TemplateInfoEvent updateEvent) { | ||
| TemplateLayerConfig layerConfig = | ||
| fti.getMetadata().get(TemplateLayerConfig.METADATA_KEY, TemplateLayerConfig.class); | ||
| if (layerConfig != null) { | ||
| Set<TemplateRule> rules = layerConfig.getTemplateRules(); | ||
| if (!rules.isEmpty()) { | ||
| TemplateInfo info = updateEvent.getSource(); | ||
| Optional<TemplateRule> rule = | ||
| rules.stream() | ||
| .filter(r -> r.getTemplateIdentifier().equals(info.getIdentifier())) | ||
| .findFirst(); | ||
| if (rule.isPresent()) { | ||
| TemplateRule r = rule.get(); | ||
| if (!r.getTemplateName().equals(info.getFullName())) | ||
| r.setTemplateName(info.getFullName()); | ||
| updateCache(info); | ||
| rules.removeIf(tr -> tr.getTemplateIdentifier().equals(info.getIdentifier())); | ||
| rules.add(r); | ||
| layerConfig.setTemplateRules(rules); | ||
| fti.getMetadata().put(TemplateLayerConfig.METADATA_KEY, layerConfig); | ||
| saveFeatureTypeInfo(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private void saveFeatureTypeInfo() { | ||
| Catalog catalog = (Catalog) GeoServerExtensions.bean("catalog"); | ||
| catalog.save(fti); | ||
| } | ||
|
|
||
| private void updateCache(TemplateInfo info) { | ||
| TemplateLoader loader = TemplateLoader.get(); | ||
| loader.cleanCache(fti, info.getIdentifier()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| import java.util.logging.Logger; | ||
| import org.geotools.util.logging.Logging; | ||
|
|
||
| public class FileTemplateDAOListener implements TemplateDAOListener { | ||
|
|
||
| private static final Logger LOGGER = Logging.getLogger(FileTemplateDAOListener.class); | ||
|
|
||
| @Override | ||
| public void handleDeleteEvent(TemplateInfoEvent deleteEvent) { | ||
| try { | ||
| TemplateFileManager.get().delete(deleteEvent.getSource()); | ||
| } catch (Exception e) { | ||
| LOGGER.warning( | ||
| "Exception while deleting template file in a TemplateInfo delete event scope. Execption is: " | ||
| + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void handleUpdateEvent(TemplateInfoEvent updateEvent) { | ||
| // do nothing | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.List; | ||
|
|
||
| public enum SupportedFormat { | ||
| JSONLD("JSON-LD"), | ||
| GML("GML"), | ||
| GEOJSON("GeoJSON"), | ||
| HTML("HTML"); | ||
|
|
||
| private String format; | ||
|
|
||
| SupportedFormat(String format) { | ||
| this.format = format; | ||
| } | ||
|
|
||
| public String getFormat() { | ||
| return this.format; | ||
| } | ||
|
|
||
| public static List<SupportedFormat> getByExtension(String extension) { | ||
| if (extension == null) return Arrays.asList(SupportedFormat.values()); | ||
| else if (extension.equals("xml")) return Arrays.asList(GML); | ||
| else if (extension.equals("xhtml")) return Arrays.asList(HTML); | ||
| else return Arrays.asList(JSONLD, GEOJSON); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| /** Base interface for listeners that can handle event issues by {@link TemplateInfoDAO} */ | ||
| public interface TemplateDAOListener { | ||
|
|
||
| /** | ||
| * Handle a TemplateInfo delete event. | ||
| * | ||
| * @param deleteEvent the delete event. | ||
| */ | ||
| void handleDeleteEvent(TemplateInfoEvent deleteEvent); | ||
|
|
||
| /** | ||
| * Handle a TemplateInfo update event. | ||
| * | ||
| * @param updateEvent | ||
| */ | ||
| void handleUpdateEvent(TemplateInfoEvent updateEvent); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| import java.io.File; | ||
| import java.io.FileOutputStream; | ||
| import java.io.IOException; | ||
| import org.geoserver.catalog.Catalog; | ||
| import org.geoserver.catalog.FeatureTypeInfo; | ||
| import org.geoserver.catalog.WorkspaceInfo; | ||
| import org.geoserver.config.GeoServerDataDirectory; | ||
| import org.geoserver.platform.GeoServerExtensions; | ||
| import org.geoserver.platform.resource.Paths; | ||
| import org.geoserver.platform.resource.Resource; | ||
|
|
||
| /** Helper class that provides methods to manage the template file. */ | ||
| public class TemplateFileManager { | ||
|
|
||
| private Catalog catalog; | ||
| private GeoServerDataDirectory dd; | ||
|
|
||
| public TemplateFileManager(Catalog catalog, GeoServerDataDirectory dd) { | ||
| this.catalog = catalog; | ||
| this.dd = dd; | ||
| } | ||
|
|
||
| /** @return the singleton instance of this class. */ | ||
| public static TemplateFileManager get() { | ||
| return GeoServerExtensions.bean(TemplateFileManager.class); | ||
| } | ||
|
|
||
| /** | ||
| * Return a {@link Resource} from a template info. | ||
| * | ||
| * @param templateInfo the template info for which we want to retrieve the corresponding | ||
| * resource. | ||
| * @return the resource that corresponds to the template info. | ||
| */ | ||
| public Resource getTemplateResource(TemplateInfo templateInfo) { | ||
| String featureType = templateInfo.getFeatureType(); | ||
| String workspace = templateInfo.getWorkspace(); | ||
| String templateName = templateInfo.getTemplateName(); | ||
| String extension = templateInfo.getExtension(); | ||
| Resource resource; | ||
| if (featureType != null) { | ||
| FeatureTypeInfo fti = catalog.getFeatureTypeByName(featureType); | ||
| resource = dd.get(fti, templateName + "." + extension); | ||
| } else if (workspace != null) { | ||
| WorkspaceInfo ws = catalog.getWorkspaceByName(workspace); | ||
| resource = dd.get(ws, templateName + "." + extension); | ||
| } else { | ||
| resource = | ||
| dd.get( | ||
| Paths.path( | ||
| TemplateInfoDAOImpl.TEMPLATE_DIR, | ||
| templateName + "." + extension)); | ||
| } | ||
| return resource; | ||
| } | ||
|
|
||
| /** | ||
| * Delete the template file associated to the template info passed as an argument. | ||
| * | ||
| * @param templateInfo the templateInfo for which we want to delete the corresponding template | ||
| * file. | ||
| * @return true if the delete process was successful false otherwise. | ||
| */ | ||
| public boolean delete(TemplateInfo templateInfo) { | ||
| return getTemplateResource(templateInfo).delete(); | ||
| } | ||
|
|
||
| /** | ||
| * Return the directory where the template file is as a File object. | ||
| * | ||
| * @param templateInfo the template info to which the desired template file is associated. | ||
| * @return the directoryu where the template file associated to the templateInfo is placed. | ||
| */ | ||
| public File getTemplateLocation(TemplateInfo templateInfo) { | ||
| String featureType = templateInfo.getFeatureType(); | ||
| String workspace = templateInfo.getWorkspace(); | ||
| Resource resource = null; | ||
| if (featureType != null) { | ||
| FeatureTypeInfo fti = catalog.getFeatureTypeByName(featureType); | ||
| resource = dd.get(fti); | ||
| } else if (workspace != null) { | ||
| WorkspaceInfo ws = catalog.getWorkspaceByName(workspace); | ||
| resource = dd.get(ws); | ||
| } else { | ||
| resource = dd.get(TemplateInfoDAOImpl.TEMPLATE_DIR); | ||
| } | ||
| File destDir = resource.dir(); | ||
| if (!destDir.exists() || !destDir.isDirectory()) { | ||
| destDir.mkdir(); | ||
| } | ||
| return destDir; | ||
| } | ||
|
|
||
| /** | ||
| * Save a template in string form to the directory defined for the template info object. | ||
| * | ||
| * @param templateInfo the template info object. | ||
| * @param rawTemplate the template content to save to a file. | ||
| */ | ||
| public void saveTemplateFile(TemplateInfo templateInfo, String rawTemplate) { | ||
| File destDir = getTemplateLocation(templateInfo); | ||
| try { | ||
| File file = | ||
| new File( | ||
| destDir, | ||
| templateInfo.getTemplateName() + "." + templateInfo.getExtension()); | ||
| if (!file.exists()) file.createNewFile(); | ||
| synchronized (this) { | ||
| try (FileOutputStream fos = new FileOutputStream(file, false)) { | ||
| fos.write(rawTemplate.getBytes()); | ||
| } | ||
| } | ||
| } catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| import java.io.Serializable; | ||
| import java.util.Objects; | ||
| import java.util.UUID; | ||
|
|
||
| /** | ||
| * This class holds the template metadata, such as the name, the identifier, the template file | ||
| * extension, workspace and featureType. | ||
| */ | ||
| public class TemplateInfo implements Serializable, Comparable<TemplateInfo> { | ||
|
|
||
| private String identifier; | ||
|
|
||
| private String description; | ||
|
|
||
| protected String templateName; | ||
|
|
||
| protected String workspace; | ||
|
|
||
| protected String featureType; | ||
|
|
||
| protected String extension; | ||
|
|
||
| public TemplateInfo() { | ||
| super(); | ||
| this.identifier = UUID.randomUUID().toString(); | ||
| } | ||
|
|
||
| public TemplateInfo( | ||
| String identifier, | ||
| String templateName, | ||
| String workspace, | ||
| String featureType, | ||
| String extension) { | ||
| this.identifier = identifier; | ||
| this.templateName = templateName; | ||
| this.workspace = workspace; | ||
| this.featureType = featureType; | ||
| this.extension = extension; | ||
| } | ||
|
|
||
| public TemplateInfo(TemplateInfo info) { | ||
| this( | ||
| info.getIdentifier(), | ||
| info.getTemplateName(), | ||
| info.getWorkspace(), | ||
| info.getFeatureType(), | ||
| info.getExtension()); | ||
| } | ||
|
|
||
| public String getTemplateName() { | ||
| return templateName; | ||
| } | ||
|
|
||
| public void setTemplateName(String templateName) { | ||
| this.templateName = templateName; | ||
| } | ||
|
|
||
| public String getWorkspace() { | ||
| return workspace; | ||
| } | ||
|
|
||
| public void setWorkspace(String workspace) { | ||
| this.workspace = workspace; | ||
| } | ||
|
|
||
| public String getFeatureType() { | ||
| return featureType; | ||
| } | ||
|
|
||
| public void setFeatureType(String featureType) { | ||
| this.featureType = featureType; | ||
| } | ||
|
|
||
| public String getDescription() { | ||
| return description; | ||
| } | ||
|
|
||
| public void setDescription(String description) { | ||
| this.description = description; | ||
| } | ||
|
|
||
| public String getExtension() { | ||
| return extension; | ||
| } | ||
|
|
||
| public void setExtension(String extension) { | ||
| this.extension = extension; | ||
| } | ||
|
|
||
| public String getIdentifier() { | ||
| return identifier; | ||
| } | ||
|
|
||
| public void setIdentifier(String identifier) { | ||
| this.identifier = identifier; | ||
| } | ||
|
|
||
| /** | ||
| * Return the full name of the Template file. By full name is meant the templateName preceded by | ||
| * the workspace name and featureTypeInfo name if defined for this instance. | ||
| * | ||
| * @return | ||
| */ | ||
| public String getFullName() { | ||
| String fullName = ""; | ||
| if (workspace != null) fullName += workspace + ":"; | ||
| if (featureType != null) fullName += featureType + ":"; | ||
| fullName += templateName; | ||
| return fullName; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object info) { | ||
| if (!super.equals(info)) return false; | ||
| if (!lenientEquals(info)) return false; | ||
| TemplateInfo templateInfo = (TemplateInfo) info; | ||
| return Objects.equals(identifier, templateInfo.identifier) | ||
| && Objects.equals(description, templateInfo.description); | ||
| } | ||
|
|
||
| public boolean lenientEquals(Object o) { | ||
| if (o == null) return false; | ||
| TemplateInfo that = (TemplateInfo) o; | ||
| return Objects.equals(templateName, that.templateName) | ||
| && Objects.equals(workspace, that.workspace) | ||
| && Objects.equals(featureType, that.featureType) | ||
| && Objects.equals(extension, that.extension); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash( | ||
| identifier, templateName, description, workspace, featureType, extension); | ||
| } | ||
|
|
||
| @Override | ||
| public int compareTo(TemplateInfo o) { | ||
| return this.templateName.compareTo(o.getTemplateName()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| import java.util.List; | ||
| import org.geoserver.catalog.FeatureTypeInfo; | ||
| import org.geoserver.platform.GeoServerExtensions; | ||
|
|
||
| /** Base interface for TemplateInfo Data Access. */ | ||
| public interface TemplateInfoDAO { | ||
|
|
||
| static TemplateInfoDAOImpl get() { | ||
| return GeoServerExtensions.bean(TemplateInfoDAOImpl.class); | ||
| } | ||
|
|
||
| public static final String TEMPLATE_DIR = "features-templating"; | ||
|
|
||
| /** @return all the saved template info. */ | ||
| public List<TemplateInfo> findAll(); | ||
|
|
||
| /** | ||
| * Find all the template info that can be used for the FeatureTypeInfo. It means that all the | ||
| * templates that are in the global directory plus all the templates in the workspace directory | ||
| * to which the FeatureTypeInfo belongs plus all the templates in the FeatureTypeInfo directory | ||
| * will be returned. | ||
| * | ||
| * @param featureTypeInfo | ||
| * @return | ||
| */ | ||
| public List<TemplateInfo> findByFeatureTypeInfo(FeatureTypeInfo featureTypeInfo); | ||
|
|
||
| /** | ||
| * @param id the identifier of the template info to retrieve. | ||
| * @return the TemplateInfo object. | ||
| */ | ||
| public TemplateInfo findById(String id); | ||
|
|
||
| /** | ||
| * Save or update the template info. | ||
| * | ||
| * @param templateData the template to save or update. | ||
| * @return the template save or updated. | ||
| */ | ||
| public TemplateInfo saveOrUpdate(TemplateInfo templateData); | ||
|
|
||
| /** | ||
| * Delete all the template info in the list. | ||
| * | ||
| * @param templateInfos list of template info to delete. | ||
| */ | ||
| public void delete(List<TemplateInfo> templateInfos); | ||
|
|
||
| /** @param templateData the template info to delete. */ | ||
| public void delete(TemplateInfo templateData); | ||
|
|
||
| /** Deletes all the template info. */ | ||
| public void deleteAll(); | ||
|
|
||
| /** | ||
| * Add a listener. | ||
| * | ||
| * @param listener the listener to add. | ||
| */ | ||
| public void addTemplateListener(TemplateDAOListener listener); | ||
|
|
||
| /** | ||
| * Find a TemplateInfo from full name. By full name is meant the name of the template file | ||
| * preceded by the workspace name and the feature type name if defined for the template. Format | ||
| * for a full name is like the following: templateName or workspaceName:templateName or | ||
| * workspaceName:featureTypeName:templateName | ||
| * | ||
| * @param fullName the full name of the template. | ||
| * @return the corresponding TemplateInfo. | ||
| */ | ||
| public TemplateInfo findByFullName(String fullName); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,332 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.io.OutputStream; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.Properties; | ||
| import java.util.Set; | ||
| import java.util.SortedSet; | ||
| import java.util.TreeSet; | ||
| import java.util.stream.Collectors; | ||
| import org.geoserver.catalog.Catalog; | ||
| import org.geoserver.catalog.CatalogException; | ||
| import org.geoserver.catalog.CatalogInfo; | ||
| import org.geoserver.catalog.FeatureTypeInfo; | ||
| import org.geoserver.catalog.WorkspaceInfo; | ||
| import org.geoserver.catalog.event.CatalogAddEvent; | ||
| import org.geoserver.catalog.event.CatalogBeforeAddEvent; | ||
| import org.geoserver.catalog.event.CatalogListener; | ||
| import org.geoserver.catalog.event.CatalogModifyEvent; | ||
| import org.geoserver.catalog.event.CatalogPostModifyEvent; | ||
| import org.geoserver.catalog.event.CatalogRemoveEvent; | ||
| import org.geoserver.config.GeoServerDataDirectory; | ||
| import org.geoserver.platform.GeoServerExtensions; | ||
| import org.geoserver.platform.resource.Paths; | ||
| import org.geoserver.platform.resource.Resource; | ||
| import org.geoserver.security.PropertyFileWatcher; | ||
|
|
||
| /** A template info DAO that use a property file for persistence. */ | ||
| public class TemplateInfoDAOImpl implements TemplateInfoDAO { | ||
|
|
||
| private SortedSet<TemplateInfo> templateDataSet; | ||
|
|
||
| private PropertyFileWatcher fileWatcher; | ||
|
|
||
| private GeoServerDataDirectory dd; | ||
|
|
||
| private Set<TemplateDAOListener> listeners; | ||
|
|
||
| private static final String PROPERTY_FILE_NAME = "features-templates-data.properties"; | ||
|
|
||
| public TemplateInfoDAOImpl(GeoServerDataDirectory dd) { | ||
| this.dd = dd; | ||
| Resource templateDir = dd.get(TEMPLATE_DIR); | ||
| File dir = templateDir.dir(); | ||
| if (!dir.exists()) dir.mkdir(); | ||
| Resource prop = dd.get(Paths.path(TEMPLATE_DIR, PROPERTY_FILE_NAME)); | ||
| prop.file(); | ||
| this.fileWatcher = new PropertyFileWatcher(prop); | ||
| this.templateDataSet = Collections.synchronizedSortedSet(new TreeSet<>()); | ||
| this.listeners = new HashSet<>(); | ||
| Catalog catalog = (Catalog) GeoServerExtensions.bean("catalog"); | ||
| catalog.addListener(new CatalogListenerTemplateInfo()); | ||
| } | ||
|
|
||
| @Override | ||
| public List<TemplateInfo> findAll() { | ||
| reloadIfNeeded(); | ||
| return new ArrayList<>(templateDataSet); | ||
| } | ||
|
|
||
| @Override | ||
| public TemplateInfo saveOrUpdate(TemplateInfo templateData) { | ||
| reloadIfNeeded(); | ||
| boolean isUpdate = | ||
| templateDataSet | ||
| .stream() | ||
| .anyMatch(ti -> ti.getIdentifier().equals(templateData.getIdentifier())); | ||
| if (isUpdate) { | ||
| fireTemplateUpdateEvent(templateData); | ||
| templateDataSet.removeIf(ti -> ti.getIdentifier().equals(templateData.getIdentifier())); | ||
| } | ||
|
|
||
| templateDataSet.add(templateData); | ||
| storeProperties(); | ||
| return templateData; | ||
| } | ||
|
|
||
| @Override | ||
| public void delete(TemplateInfo templateData) { | ||
| reloadIfNeeded(); | ||
| templateDataSet.remove(templateData); | ||
| fireTemplateInfoRemoveEvent(templateData); | ||
| storeProperties(); | ||
| } | ||
|
|
||
| @Override | ||
| public void delete(List<TemplateInfo> templateInfos) { | ||
| reloadIfNeeded(); | ||
| templateDataSet.removeAll(templateInfos); | ||
| storeProperties(); | ||
| for (TemplateInfo ti : templateInfos) fireTemplateInfoRemoveEvent(ti); | ||
| } | ||
|
|
||
| @Override | ||
| public void deleteAll() { | ||
| reloadIfNeeded(); | ||
| Set<TemplateInfo> templateInfos = templateDataSet; | ||
| templateDataSet = Collections.synchronizedSortedSet(new TreeSet<>()); | ||
| storeProperties(); | ||
| for (TemplateInfo ti : templateInfos) fireTemplateInfoRemoveEvent(ti); | ||
| } | ||
|
|
||
| @Override | ||
| public TemplateInfo findById(String id) { | ||
| reloadIfNeeded(); | ||
| Optional<TemplateInfo> optional = | ||
| templateDataSet.stream().filter(ti -> ti.getIdentifier().equals(id)).findFirst(); | ||
| if (optional.isPresent()) return optional.get(); | ||
| else return null; | ||
| } | ||
|
|
||
| @Override | ||
| public TemplateInfo findByFullName(String fullName) { | ||
| reloadIfNeeded(); | ||
| Optional<TemplateInfo> templateInfo = | ||
| templateDataSet | ||
| .stream() | ||
| .filter(ti -> ti.getFullName().equals(fullName)) | ||
| .findFirst(); | ||
| if (templateInfo.isPresent()) return templateInfo.get(); | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public List<TemplateInfo> findByFeatureTypeInfo(FeatureTypeInfo featureTypeInfo) { | ||
| reloadIfNeeded(); | ||
| String workspace = featureTypeInfo.getStore().getWorkspace().getName(); | ||
| String name = featureTypeInfo.getName(); | ||
| return templateDataSet | ||
| .stream() | ||
| .filter( | ||
| ti -> | ||
| (ti.getWorkspace() == null && ti.getFeatureType() == null) | ||
| || ti.getFeatureType() == null | ||
| && ti.getWorkspace().equals(workspace) | ||
| || (ti.getWorkspace().equals(workspace) | ||
| && ti.getFeatureType().equals(name))) | ||
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| private void fireTemplateUpdateEvent(TemplateInfo templateInfo) { | ||
| for (TemplateDAOListener listener : listeners) { | ||
| listener.handleUpdateEvent(new TemplateInfoEvent(templateInfo)); | ||
| } | ||
| } | ||
|
|
||
| private void fireTemplateInfoRemoveEvent(TemplateInfo templateInfo) { | ||
| for (TemplateDAOListener listener : listeners) { | ||
| listener.handleDeleteEvent(new TemplateInfoEvent(templateInfo)); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void addTemplateListener(TemplateDAOListener listener) { | ||
| this.listeners.add(listener); | ||
| } | ||
|
|
||
| private TemplateInfo parseProperty(String key, String value) { | ||
| TemplateInfo templateData = new TemplateInfo(); | ||
| templateData.setIdentifier(key); | ||
| String[] values = value.split(";"); | ||
| for (String v : values) { | ||
| String[] attribute = v.split("="); | ||
| String attrName = attribute[0]; | ||
| String attrValue = attribute[1]; | ||
| if (attrName.equals("templateName")) templateData.setTemplateName(attrValue); | ||
| else if (attrName.equals("extension")) templateData.setExtension(attrValue); | ||
| else if (attrName.equals("workspace")) templateData.setWorkspace(attrValue); | ||
| else if (attrName.equals("featureTypeInfo")) templateData.setFeatureType(attrValue); | ||
| } | ||
| templateData.setIdentifier(key); | ||
| return templateData; | ||
| } | ||
|
|
||
| private Properties toProperties() { | ||
| Properties properties = new Properties(); | ||
| for (TemplateInfo td : templateDataSet) { | ||
| StringBuilder sb = new StringBuilder(); | ||
| sb.append("templateName=") | ||
| .append(td.getTemplateName()) | ||
| .append(";extension=") | ||
| .append(td.getExtension()); | ||
| String ws = td.getWorkspace(); | ||
| if (ws != null) sb.append(";workspace=").append(td.getWorkspace()); | ||
| String fti = td.getFeatureType(); | ||
| if (fti != null) sb.append(";featureTypeInfo=").append(td.getFeatureType()); | ||
| properties.put(td.getIdentifier(), sb.toString()); | ||
| } | ||
| return properties; | ||
| } | ||
|
|
||
| public void storeProperties() { | ||
| Properties p = toProperties(); | ||
| Resource propFile = dd.get(Paths.path(TEMPLATE_DIR, PROPERTY_FILE_NAME)); | ||
| try (OutputStream os = propFile.out()) { | ||
| p.store(os, null); | ||
| } catch (Exception e) { | ||
| throw new RuntimeException("Could not write rules to " + PROPERTY_FILE_NAME); | ||
| } | ||
| } | ||
|
|
||
| private boolean isModified() { | ||
| return fileWatcher != null && fileWatcher.isStale(); | ||
| } | ||
|
|
||
| private void loadTemplateInfo() { | ||
| try { | ||
| Properties properties = fileWatcher.getProperties(); | ||
| this.templateDataSet = Collections.synchronizedSortedSet(new TreeSet<>()); | ||
| for (Object k : properties.keySet()) { | ||
| TemplateInfo td = parseProperty(k.toString(), properties.getProperty(k.toString())); | ||
| this.templateDataSet.add(td); | ||
| } | ||
| } catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| private void reloadIfNeeded() { | ||
| if (isModified() || templateDataSet.isEmpty()) loadTemplateInfo(); | ||
| } | ||
|
|
||
| public static class CatalogListenerTemplateInfo implements CatalogListener { | ||
| @Override | ||
| public void handlePreAddEvent(CatalogBeforeAddEvent event) throws CatalogException {} | ||
|
|
||
| @Override | ||
| public void handleAddEvent(CatalogAddEvent event) throws CatalogException {} | ||
|
|
||
| @Override | ||
| public void handleRemoveEvent(CatalogRemoveEvent event) throws CatalogException { | ||
| CatalogInfo source = event.getSource(); | ||
| if (source instanceof FeatureTypeInfo) { | ||
| removeFtTemplates((FeatureTypeInfo) source); | ||
|
|
||
| } else if (source instanceof WorkspaceInfo) { | ||
| removeWSTemplates((WorkspaceInfo) source); | ||
| } | ||
| } | ||
|
|
||
| private void removeFtTemplates(FeatureTypeInfo ft) { | ||
| TemplateInfoDAO dao = TemplateInfoDAO.get(); | ||
| List<TemplateInfo> templateInfos = dao.findByFeatureTypeInfo(ft); | ||
| dao.delete( | ||
| templateInfos | ||
| .stream() | ||
| .filter(ti -> ti.getFeatureType() != null) | ||
| .collect(Collectors.toList())); | ||
| } | ||
|
|
||
| private void removeWSTemplates(WorkspaceInfo ws) { | ||
| TemplateInfoDAO dao = TemplateInfoDAO.get(); | ||
| List<TemplateInfo> templateInfos = | ||
| dao.findAll() | ||
| .stream() | ||
| .filter(ti -> ti.getWorkspace().equals(ws.getName())) | ||
| .collect(Collectors.toList()); | ||
| dao.delete(templateInfos); | ||
| } | ||
|
|
||
| @Override | ||
| public void handleModifyEvent(CatalogModifyEvent event) throws CatalogException { | ||
| final CatalogInfo source = event.getSource(); | ||
| if (source instanceof FeatureTypeInfo) { | ||
| int nameIdx = event.getPropertyNames().indexOf("name"); | ||
| if (nameIdx != -1) { | ||
| String newName = (String) event.getNewValues().get(nameIdx); | ||
| updateTemplateInfoLayerName((FeatureTypeInfo) source, newName); | ||
| } | ||
| } else if (source instanceof WorkspaceInfo) { | ||
| int nameIdx = event.getPropertyNames().indexOf("name"); | ||
| if (nameIdx != -1) { | ||
| String oldName = (String) event.getOldValues().get(nameIdx); | ||
| String newName = (String) event.getNewValues().get(nameIdx); | ||
| updateWorkspaceNames(oldName, newName); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private void updateTemplateInfoLayerName(FeatureTypeInfo fti, String newName) { | ||
| TemplateInfoDAO dao = TemplateInfoDAO.get(); | ||
| List<TemplateInfo> templateInfo = dao.findByFeatureTypeInfo(fti); | ||
| for (TemplateInfo ti : templateInfo) { | ||
| ti.setFeatureType(newName); | ||
| } | ||
| ((TemplateInfoDAOImpl) dao).storeProperties(); | ||
| } | ||
|
|
||
| private void updateTemplateInfoWorkspace(WorkspaceInfo wi, FeatureTypeInfo fti) { | ||
| TemplateInfoDAO dao = TemplateInfoDAO.get(); | ||
| List<TemplateInfo> templateInfo = dao.findByFeatureTypeInfo(fti); | ||
| for (TemplateInfo ti : templateInfo) { | ||
| ti.setWorkspace(wi.getName()); | ||
| } | ||
| ((TemplateInfoDAOImpl) dao).storeProperties(); | ||
| } | ||
|
|
||
| private void updateWorkspaceNames(String oldName, String newName) { | ||
| TemplateInfoDAO dao = TemplateInfoDAO.get(); | ||
| List<TemplateInfo> infos = dao.findAll(); | ||
| for (TemplateInfo ti : infos) { | ||
| if (ti.getWorkspace().equals(oldName)) ti.setWorkspace(newName); | ||
| } | ||
| ((TemplateInfoDAOImpl) dao).storeProperties(); | ||
| } | ||
|
|
||
| @Override | ||
| public void handlePostModifyEvent(CatalogPostModifyEvent event) throws CatalogException { | ||
| CatalogInfo source = event.getSource(); | ||
| if (source instanceof FeatureTypeInfo) { | ||
| FeatureTypeInfo info = (FeatureTypeInfo) source; | ||
| int wsIdx = event.getPropertyNames().indexOf("workspace"); | ||
| if (wsIdx != -1) { | ||
| WorkspaceInfo newWorkspace = (WorkspaceInfo) event.getNewValues().get(wsIdx); | ||
| updateTemplateInfoWorkspace(newWorkspace, info); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void reloaded() {} | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| /** A TemplateInfo event. Simply hold the TemplateInfo object affected by the event. */ | ||
| public class TemplateInfoEvent { | ||
|
|
||
| private TemplateInfo ti; | ||
|
|
||
| public TemplateInfoEvent(TemplateInfo templateInfo) { | ||
| this.ti = templateInfo; | ||
| } | ||
|
|
||
| public TemplateInfo getSource() { | ||
| return ti; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| import java.io.Serializable; | ||
| import java.util.HashSet; | ||
| import java.util.Set; | ||
| import javax.xml.bind.annotation.XmlElement; | ||
| import javax.xml.bind.annotation.XmlRootElement; | ||
|
|
||
| /** | ||
| * A class that holds a list of {@link TemplateRule}. Is meant to be stored in the FeatureTypeInfo | ||
| * metadata map. | ||
| */ | ||
| @XmlRootElement(name = "TemplateLayerConfig") | ||
| public class TemplateLayerConfig implements Serializable { | ||
|
|
||
| public static final String METADATA_KEY = "FEATURES_TEMPLATING_LAYER_CONF"; | ||
|
|
||
| @XmlElement(name = "Rule") | ||
| private Set<TemplateRule> templateRules; | ||
|
|
||
| public TemplateLayerConfig(Set<TemplateRule> templateRules) { | ||
| this.templateRules = templateRules; | ||
| } | ||
|
|
||
| public TemplateLayerConfig() { | ||
| templateRules = new HashSet<>(); | ||
| } | ||
|
|
||
| public void addTemplateRule(TemplateRule rule) { | ||
| if (this.templateRules == null) templateRules = new HashSet<>(); | ||
| this.templateRules.add(rule); | ||
| } | ||
|
|
||
| public Set<TemplateRule> getTemplateRules() { | ||
| if (this.templateRules == null) this.templateRules = new HashSet<>(); | ||
| return templateRules; | ||
| } | ||
|
|
||
| public void setTemplateRules(Set<TemplateRule> templateRules) { | ||
| this.templateRules = templateRules; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| import org.geoserver.config.util.XStreamPersister; | ||
| import org.geoserver.config.util.XStreamPersisterInitializer; | ||
|
|
||
| /** XStreamPersisterInitializer for TemplateLayerConfig class and TemplateRule list. */ | ||
| public class TemplateLayerConfigXStreamPersisterInitializer implements XStreamPersisterInitializer { | ||
|
|
||
| @Override | ||
| public void init(XStreamPersister persister) { | ||
| persister.getXStream().alias("Rule", TemplateRule.class); | ||
| persister.getXStream().alias("TemplateLayerConfig", TemplateLayerConfig.class); | ||
| persister.registerBreifMapComplexType("TemplateRuleType", TemplateRule.class); | ||
| persister.registerBreifMapComplexType("LayerConfigType", TemplateLayerConfig.class); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,263 @@ | ||
| /* (c) 2019 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.featurestemplating.configuration; | ||
|
|
||
| import com.google.common.cache.CacheBuilder; | ||
| import com.google.common.cache.CacheLoader; | ||
| import com.google.common.cache.LoadingCache; | ||
| import java.io.IOException; | ||
| import java.util.ArrayList; | ||
| import java.util.Iterator; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.Set; | ||
| import java.util.concurrent.ExecutionException; | ||
| import java.util.concurrent.TimeUnit; | ||
| import org.eclipse.emf.common.util.URI; | ||
| import org.geoserver.catalog.FeatureTypeInfo; | ||
| import org.geoserver.config.GeoServerDataDirectory; | ||
| import org.geoserver.featurestemplating.builders.impl.RootBuilder; | ||
| import org.geoserver.featurestemplating.builders.visitors.SimplifiedPropertyReplacer; | ||
| import org.geoserver.featurestemplating.readers.TemplateReaderConfiguration; | ||
| import org.geoserver.featurestemplating.validation.TemplateValidator; | ||
| import org.geoserver.ows.Dispatcher; | ||
| import org.geoserver.ows.Request; | ||
| import org.geoserver.platform.GeoServerExtensions; | ||
| import org.geoserver.platform.resource.Resource; | ||
| import org.geotools.data.complex.AppSchemaDataAccessRegistry; | ||
| import org.geotools.data.complex.DataAccessRegistry; | ||
| import org.geotools.data.complex.FeatureTypeMapping; | ||
| import org.geotools.data.complex.feature.type.ComplexFeatureTypeImpl; | ||
| import org.geotools.data.complex.feature.type.Types; | ||
| import org.opengis.feature.type.FeatureType; | ||
| import org.xml.sax.helpers.NamespaceSupport; | ||
|
|
||
| /** Manage the cache and the retrieving for all templates files */ | ||
| public class TemplateLoader { | ||
|
|
||
| private final LoadingCache<CacheKey, Template> templateCache; | ||
| private GeoServerDataDirectory dataDirectory; | ||
|
|
||
| public TemplateLoader(GeoServerDataDirectory dd) { | ||
| this.dataDirectory = dd; | ||
| templateCache = | ||
| CacheBuilder.newBuilder() | ||
| .maximumSize(100) | ||
| .initialCapacity(1) | ||
| .expireAfterAccess(120, TimeUnit.MINUTES) | ||
| .build(new TemplateCacheLoader()); | ||
| } | ||
|
|
||
| /** | ||
| * Get the template related to the featureType. If template has been modified updates the cache | ||
| * with the new Template | ||
| */ | ||
| public RootBuilder getTemplate(FeatureTypeInfo typeInfo, String outputFormat) | ||
| throws ExecutionException { | ||
| String templateIdentifier = evaluatesTemplateRule(typeInfo); | ||
| if (templateIdentifier == null) | ||
| templateIdentifier = TemplateIdentifier.fromOutputFormat(outputFormat).getFilename(); | ||
| CacheKey key = new CacheKey(typeInfo, templateIdentifier); | ||
| Template template = templateCache.get(key); | ||
| boolean updateCache = false; | ||
| if (template.checkTemplate()) updateCache = true; | ||
|
|
||
| RootBuilder root = template.getRootBuilder(); | ||
|
|
||
| if (updateCache) { | ||
| replaceSimplifiedPropertiesIfNeeded(key.getResource(), template.getRootBuilder()); | ||
| templateCache.put(key, template); | ||
| } | ||
|
|
||
| if (root != null) { | ||
| TemplateValidator validator = new TemplateValidator(typeInfo); | ||
| boolean isValid = validator.validateTemplate(root); | ||
| if (!isValid) { | ||
| throw new RuntimeException( | ||
| "Failed to validate template for feature type " | ||
| + typeInfo.getName() | ||
| + ". Failing attribute is " | ||
| + URI.decode(validator.getFailingAttribute())); | ||
| } | ||
| } | ||
| return root; | ||
| } | ||
|
|
||
| /** | ||
| * Extract Namespaces from given FeatureType | ||
| * | ||
| * @return Namespaces if found for the given FeatureType | ||
| */ | ||
| private NamespaceSupport declareNamespaces(FeatureType type) { | ||
| NamespaceSupport namespaceSupport = null; | ||
| if (type instanceof ComplexFeatureTypeImpl) { | ||
| Map namespaces = (Map) type.getUserData().get(Types.DECLARED_NAMESPACES_MAP); | ||
| if (namespaces != null) { | ||
| namespaceSupport = new NamespaceSupport(); | ||
| for (Iterator it = namespaces.entrySet().iterator(); it.hasNext(); ) { | ||
| Map.Entry entry = (Map.Entry) it.next(); | ||
| String prefix = (String) entry.getKey(); | ||
| String namespace = (String) entry.getValue(); | ||
| namespaceSupport.declarePrefix(prefix, namespace); | ||
| } | ||
| } | ||
| } | ||
| return namespaceSupport; | ||
| } | ||
|
|
||
| GeoServerDataDirectory getDataDirectory() { | ||
| return dataDirectory; | ||
| } | ||
|
|
||
| private class CacheKey { | ||
| private FeatureTypeInfo resource; | ||
| private String templateIdentifier; | ||
|
|
||
| public CacheKey(FeatureTypeInfo resource, String templateIdentifier) { | ||
| this.resource = resource; | ||
| this.templateIdentifier = templateIdentifier; | ||
| } | ||
|
|
||
| public FeatureTypeInfo getResource() { | ||
| return resource; | ||
| } | ||
|
|
||
| public String getTemplateIdentifier() { | ||
| return templateIdentifier; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (!(o instanceof CacheKey)) return false; | ||
| CacheKey other = (CacheKey) o; | ||
| if (!other.getTemplateIdentifier().equals(templateIdentifier)) return false; | ||
| else if (!(other.getResource().getName().equals(resource.getName()))) return false; | ||
| else if (!(other.getResource().getNamespace().equals(resource.getNamespace()))) | ||
| return false; | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash(resource, templateIdentifier); | ||
| } | ||
| } | ||
|
|
||
| private void replaceSimplifiedPropertiesIfNeeded( | ||
| FeatureTypeInfo featureTypeInfo, RootBuilder rootBuilder) { | ||
| try { | ||
| if (featureTypeInfo.getFeatureType() instanceof ComplexFeatureTypeImpl | ||
| && rootBuilder != null) { | ||
|
|
||
| DataAccessRegistry registry = AppSchemaDataAccessRegistry.getInstance(); | ||
| FeatureTypeMapping featureTypeMapping = | ||
| registry.mappingByElement(featureTypeInfo.getQualifiedNativeName()); | ||
| if (featureTypeMapping != null) { | ||
| SimplifiedPropertyReplacer visitor = | ||
| new SimplifiedPropertyReplacer(featureTypeMapping); | ||
| rootBuilder.accept(visitor, null); | ||
| } | ||
| } | ||
| } catch (Exception e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| // evaluates the template rule associated to the featureTypeInfo and return the TemplateInfo id. | ||
| private String evaluatesTemplateRule(FeatureTypeInfo featureTypeInfo) { | ||
| List<TemplateRule> matching = new ArrayList<>(); | ||
| TemplateLayerConfig config = | ||
| featureTypeInfo | ||
| .getMetadata() | ||
| .get(TemplateLayerConfig.METADATA_KEY, TemplateLayerConfig.class); | ||
| if (config == null || config.getTemplateRules().isEmpty()) return null; | ||
| else { | ||
| Set<TemplateRule> rules = config.getTemplateRules(); | ||
| Request request = Dispatcher.REQUEST.get(); | ||
| for (TemplateRule r : rules) { | ||
| if (r.applyRule(request)) matching.add(r); | ||
| } | ||
| } | ||
| int size = matching.size(); | ||
| if (size > 0) { | ||
| if (size > 1) { | ||
| TemplateRule.TemplateRuleComparator comparator = | ||
| new TemplateRule.TemplateRuleComparator(); | ||
| matching.sort(comparator); | ||
| } | ||
| return matching.get(0).getTemplateIdentifier(); | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Remove from the cache the entry with the specified identifier and Feature Type | ||
| * | ||
| * @param fti the FeatureType to which is associated the entry. | ||
| * @param templateIdentifier the templateIdentifier of the cached template. | ||
| */ | ||
| public void cleanCache(FeatureTypeInfo fti, String templateIdentifier) { | ||
| CacheKey key = new CacheKey(fti, templateIdentifier); | ||
| if (templateCache.getIfPresent(key) != null) this.templateCache.invalidate(key); | ||
| } | ||
|
|
||
| /** | ||
| * Remove all the cached entries with the specified templateIdentifier. | ||
| * | ||
| * @param templateIdentifier the templateIdentifier used to identify the cache entries to | ||
| * remove. | ||
| */ | ||
| public void removeAllWithIdentifier(String templateIdentifier) { | ||
| Set<CacheKey> keys = templateCache.asMap().keySet(); | ||
| for (CacheKey key : keys) { | ||
| if (key.getTemplateIdentifier().equals(templateIdentifier)) { | ||
| templateCache.invalidate(key); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private TemplateFileManager getTemplateFileManager() { | ||
| return TemplateFileManager.get(); | ||
| } | ||
|
|
||
| private class TemplateCacheLoader extends CacheLoader<CacheKey, Template> { | ||
| @Override | ||
| public Template load(CacheKey key) { | ||
| NamespaceSupport namespaces = null; | ||
| try { | ||
| FeatureType type = key.getResource().getFeatureType(); | ||
| namespaces = declareNamespaces(type); | ||
| } catch (IOException e) { | ||
| throw new RuntimeException( | ||
| "Error retrieving FeatureType " | ||
| + key.getResource().getName() | ||
| + "Exception is: " | ||
| + e.getMessage()); | ||
| } | ||
| TemplateInfo templateInfo = TemplateInfoDAO.get().findById(key.getTemplateIdentifier()); | ||
| Resource resource; | ||
| if (templateInfo != null) | ||
| resource = getTemplateFileManager().getTemplateResource(templateInfo); | ||
| else resource = getDataDirectory().get(key.getResource(), key.getTemplateIdentifier()); | ||
| Template template = new Template(resource, new TemplateReaderConfiguration(namespaces)); | ||
| RootBuilder builder = template.getRootBuilder(); | ||
| if (builder != null) { | ||
| replaceSimplifiedPropertiesIfNeeded(key.getResource(), builder); | ||
| } | ||
| return template; | ||
| } | ||
| } | ||
|
|
||
| /** Invalidate all the cache entries. */ | ||
| public void reset() { | ||
| templateCache.invalidateAll(); | ||
| } | ||
|
|
||
| public static TemplateLoader get() { | ||
| return GeoServerExtensions.bean(TemplateLoader.class); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| import org.geoserver.config.impl.GeoServerLifecycleHandler; | ||
|
|
||
| /** | ||
| * Cleans the cache whenever the reset/reload config is triggered. Mostly useful for tests and REST | ||
| * automation, as the file watcher won't reload files checked less than a second ago. | ||
| */ | ||
| public class TemplateReloader implements GeoServerLifecycleHandler { | ||
|
|
||
| TemplateLoader loader; | ||
|
|
||
| public TemplateReloader(TemplateLoader configuration) { | ||
| this.loader = configuration; | ||
| } | ||
|
|
||
| @Override | ||
| public void onReset() { | ||
| loader.reset(); | ||
| } | ||
|
|
||
| @Override | ||
| public void onDispose() {} | ||
|
|
||
| @Override | ||
| public void beforeReload() {} | ||
|
|
||
| @Override | ||
| public void onReload() { | ||
| loader.reset(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,253 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| import java.io.Serializable; | ||
| import java.util.Comparator; | ||
| import java.util.Objects; | ||
| import java.util.UUID; | ||
| import javax.xml.bind.annotation.XmlRootElement; | ||
| import org.geoserver.ows.Request; | ||
| import org.geoserver.util.XCQL; | ||
| import org.geotools.filter.text.cql2.CQLException; | ||
|
|
||
| /** | ||
| * A template rule associated to a FeatureTypeInfo. Its evaluation determines if a specific template | ||
| * should be applied for a Request. | ||
| */ | ||
| @XmlRootElement(name = "Rule") | ||
| public class TemplateRule implements Serializable { | ||
|
|
||
| private String ruleId; | ||
|
|
||
| private Integer priority; | ||
|
|
||
| private String templateIdentifier; | ||
|
|
||
| private String templateName; | ||
|
|
||
| private String outputFormat; | ||
|
|
||
| private String service; | ||
|
|
||
| private String cqlFilter; | ||
|
|
||
| private String profileFilter; | ||
|
|
||
| // use to force a rule to be applied regardless of priority | ||
| // currently used only from the preview mechanism in the web module. | ||
| private boolean forceRule; | ||
|
|
||
| public TemplateRule() { | ||
| this.priority = 0; | ||
| this.ruleId = UUID.randomUUID().toString(); | ||
| } | ||
|
|
||
| public TemplateRule(TemplateRule rule) { | ||
| this.ruleId = rule.ruleId == null ? UUID.randomUUID().toString() : rule.ruleId; | ||
| this.priority = rule.priority; | ||
| this.outputFormat = rule.outputFormat; | ||
| this.cqlFilter = rule.cqlFilter; | ||
| this.service = rule.service; | ||
| this.forceRule = rule.forceRule; | ||
| this.templateName = rule.templateName; | ||
| this.templateIdentifier = rule.templateIdentifier; | ||
| this.profileFilter = rule.profileFilter; | ||
| } | ||
|
|
||
| public String getTemplateName() { | ||
| return templateName; | ||
| } | ||
|
|
||
| /** | ||
| * Apply the rule to the Request to see if it matches it. | ||
| * | ||
| * @param request the request against which evaluate the rule. | ||
| * @return | ||
| */ | ||
| public boolean applyRule(Request request) { | ||
| boolean result = true; | ||
| if (outputFormat != null) result = matchOutputFormat(getOutputFormat(request)); | ||
|
|
||
| if (result && cqlFilter != null) { | ||
| result = evaluateCQLFilter(cqlFilter); | ||
| } | ||
| if (result && profileFilter != null) result = evaluateCQLFilter(profileFilter); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| private boolean evaluateCQLFilter(String filter) { | ||
| try { | ||
| return XCQL.toFilter(filter).evaluate(null); | ||
| } catch (CQLException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| public void setTemplateName(String templateName) { | ||
| this.templateName = templateName; | ||
| } | ||
|
|
||
| public SupportedFormat getOutputFormat() { | ||
| if (outputFormat != null) return SupportedFormat.valueOf(outputFormat); | ||
| return null; | ||
| } | ||
|
|
||
| public void setOutputFormat(SupportedFormat outputFormat) { | ||
| this.outputFormat = outputFormat.name(); | ||
| } | ||
|
|
||
| public String getService() { | ||
| return service; | ||
| } | ||
|
|
||
| public void setService(String service) { | ||
| this.service = service; | ||
| } | ||
|
|
||
| public String getCqlFilter() { | ||
| return cqlFilter; | ||
| } | ||
|
|
||
| public void setCqlFilter(String cqlFilter) { | ||
| this.cqlFilter = cqlFilter; | ||
| } | ||
|
|
||
| public String getTemplateIdentifier() { | ||
| return templateIdentifier; | ||
| } | ||
|
|
||
| public void setTemplateIdentifier(String templateIdentifier) { | ||
| this.templateIdentifier = templateIdentifier; | ||
| } | ||
|
|
||
| private boolean matchOutputFormat(String outputFormat) { | ||
| TemplateIdentifier identifier = TemplateIdentifier.fromOutputFormat(outputFormat); | ||
| if (identifier == null) return false; | ||
| String nameIdentifier = identifier.name(); | ||
| if (this.outputFormat.equals(SupportedFormat.GML.name())) | ||
| return nameIdentifier.startsWith(this.outputFormat); | ||
| else if (this.outputFormat.equals(SupportedFormat.GEOJSON.name())) | ||
| return nameIdentifier.equals(TemplateIdentifier.GEOJSON.name()) | ||
| || nameIdentifier.equals(TemplateIdentifier.JSON.name()); | ||
| else if (this.outputFormat.equals(SupportedFormat.HTML.name())) | ||
| return nameIdentifier.equals(TemplateIdentifier.HTML.name()); | ||
| else return nameIdentifier.equals(this.outputFormat); | ||
| } | ||
|
|
||
| public void setTemplateInfo(TemplateInfo templateInfo) { | ||
| if (templateInfo != null) { | ||
| this.templateName = templateInfo.getFullName(); | ||
| this.templateIdentifier = templateInfo.getIdentifier(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Return the TemplateInfo to which this rule refers to. | ||
| * | ||
| * @return the TemplateInfo associated to the rule. | ||
| */ | ||
| public TemplateInfo getTemplateInfo() { | ||
| TemplateInfo ti = new TemplateInfo(); | ||
| if (templateName != null && templateName.indexOf(":") != -1) { | ||
| String[] nameSplit = templateName.split(":"); | ||
| if (nameSplit.length == 3) { | ||
| ti.setWorkspace(nameSplit[0]); | ||
| ti.setFeatureType(nameSplit[1]); | ||
| ti.setTemplateName(nameSplit[2]); | ||
| } else { | ||
| ti.setWorkspace(nameSplit[0]); | ||
| ti.setTemplateName(nameSplit[1]); | ||
| } | ||
| } | ||
| ti.setIdentifier(templateIdentifier); | ||
| return ti; | ||
| } | ||
|
|
||
| private String getOutputFormat(Request request) { | ||
| String outputFormat = request.getOutputFormat(); | ||
| if (outputFormat == null) | ||
| outputFormat = request.getKvp() != null ? (String) request.getKvp().get("f") : null; | ||
| if (outputFormat == null) | ||
| outputFormat = | ||
| request.getKvp() != null ? (String) request.getKvp().get("INFO_FORMAT") : null; | ||
| return outputFormat; | ||
| } | ||
|
|
||
| public String getRuleId() { | ||
| return ruleId; | ||
| } | ||
|
|
||
| public void setRuleId(String ruleId) { | ||
| this.ruleId = ruleId; | ||
| } | ||
|
|
||
| public boolean isForceRule() { | ||
| return forceRule; | ||
| } | ||
|
|
||
| public void setForceRule(boolean forceRule) { | ||
| this.forceRule = forceRule; | ||
| } | ||
|
|
||
| public Integer getPriority() { | ||
| return priority; | ||
| } | ||
|
|
||
| public void setPriority(Integer priority) { | ||
| this.priority = priority; | ||
| } | ||
|
|
||
| public String getProfileFilter() { | ||
| return profileFilter; | ||
| } | ||
|
|
||
| public void setProfileFilter(String profileFilter) { | ||
| this.profileFilter = profileFilter; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) return true; | ||
| if (o == null || getClass() != o.getClass()) return false; | ||
| TemplateRule that = (TemplateRule) o; | ||
| return Objects.equals(templateIdentifier, that.templateIdentifier) | ||
| && Objects.equals(templateName, that.templateName) | ||
| && Objects.equals(outputFormat, that.outputFormat) | ||
| && Objects.equals(service, that.service) | ||
| && Objects.equals(profileFilter, that.profileFilter) | ||
| && Objects.equals(cqlFilter, that.cqlFilter) | ||
| && Objects.equals(priority, that.priority); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash( | ||
| templateIdentifier, templateName, outputFormat, service, cqlFilter, priority); | ||
| } | ||
|
|
||
| /** | ||
| * Rule comparator to sort the TemplateRules in order to get the one with higher priority or the | ||
| * one that is forced. | ||
| */ | ||
| public static class TemplateRuleComparator implements Comparator<TemplateRule> { | ||
|
|
||
| @Override | ||
| public int compare(TemplateRule o1, TemplateRule o2) { | ||
| int result; | ||
| if (o1.isForceRule()) result = -1; | ||
| else if (o2.isForceRule()) result = 1; | ||
| else { | ||
| int p1 = o1.getPriority(); | ||
| int p2 = o2.getPriority(); | ||
| if (p1 < p2) result = -1; | ||
| else if (p2 < p1) result = 1; | ||
| else result = 0; | ||
| } | ||
| return result; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.Set; | ||
| import org.geoserver.catalog.Catalog; | ||
| import org.geoserver.catalog.FeatureTypeInfo; | ||
| import org.geoserver.platform.GeoServerExtensions; | ||
|
|
||
| /** Class that provides methods to add, update or delete Template Rules */ | ||
| public class TemplateRuleService { | ||
|
|
||
| private FeatureTypeInfo featureTypeInfo; | ||
|
|
||
| public TemplateRuleService(FeatureTypeInfo featureTypeInfo) { | ||
| this.featureTypeInfo = featureTypeInfo; | ||
| } | ||
|
|
||
| /** | ||
| * Remove the template rule having the specified id. | ||
| * | ||
| * @param ruleId the id of the rule to delete. | ||
| * @return true if the rule was deleted false if the rule was not found. | ||
| */ | ||
| public boolean removeRule(String ruleId) { | ||
| boolean result = false; | ||
| Set<TemplateRule> rules = getRules(); | ||
| if (rules != null && !rules.isEmpty()) { | ||
| result = rules.removeIf(r -> r.getRuleId().equals(ruleId)); | ||
| if (result) { | ||
| TemplateLayerConfig config = getTemplateLayerConfig(); | ||
| config.setTemplateRules(rules); | ||
| featureTypeInfo.getMetadata().put(TemplateLayerConfig.METADATA_KEY, config); | ||
| getCatalog().save(featureTypeInfo); | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * Replace the rule with the one passed as an argument if they have the same id. | ||
| * | ||
| * @param rule the rule to use as a replacement for the one with same id. | ||
| */ | ||
| public void replaceRule(TemplateRule rule) { | ||
| Set<TemplateRule> rules = getRules(); | ||
| if (rules != null) { | ||
| if (rules.removeIf((r -> r.getRuleId().equals(rule.getRuleId())))) { | ||
| Set<TemplateRule> ruleset = updatePriorities(new ArrayList<>(rules), rule); | ||
| TemplateLayerConfig config = getTemplateLayerConfig(); | ||
| config.setTemplateRules(ruleset); | ||
| featureTypeInfo.getMetadata().put(TemplateLayerConfig.METADATA_KEY, config); | ||
| getCatalog().save(featureTypeInfo); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Save a rule. | ||
| * | ||
| * @param rule the rule to save. | ||
| */ | ||
| public void saveRule(TemplateRule rule) { | ||
| TemplateLayerConfig config = getTemplateLayerConfig(); | ||
| if (config == null) config = new TemplateLayerConfig(); | ||
| Set<TemplateRule> rules = config.getTemplateRules(); | ||
| Set<TemplateRule> ruleset = updatePriorities(new ArrayList<>(rules), rule); | ||
| config.setTemplateRules(ruleset); | ||
| featureTypeInfo.getMetadata().put(TemplateLayerConfig.METADATA_KEY, config); | ||
| getCatalog().save(featureTypeInfo); | ||
| } | ||
|
|
||
| private TemplateLayerConfig getTemplateLayerConfig() { | ||
| return featureTypeInfo | ||
| .getMetadata() | ||
| .get(TemplateLayerConfig.METADATA_KEY, TemplateLayerConfig.class); | ||
| } | ||
|
|
||
| public Set<TemplateRule> getRules() { | ||
| TemplateLayerConfig layerConfig = getTemplateLayerConfig(); | ||
| if (layerConfig != null) return layerConfig.getTemplateRules(); | ||
| return Collections.emptySet(); | ||
| } | ||
|
|
||
| /** | ||
| * Get the template rule having the specified id. | ||
| * | ||
| * @param ruleId the id of the rule to find. | ||
| * @return the rule or null if not found. | ||
| */ | ||
| public TemplateRule getRule(String ruleId) { | ||
| Set<TemplateRule> rules = getRules(); | ||
| if (rules != null && !rules.isEmpty()) { | ||
| Optional<TemplateRule> opRule = | ||
| rules.stream().filter(r -> r.getRuleId().equals(ruleId)).findFirst(); | ||
| if (opRule.isPresent()) return opRule.get(); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private Catalog getCatalog() { | ||
| return (Catalog) GeoServerExtensions.bean("catalog"); | ||
| } | ||
|
|
||
| /** | ||
| * Update the priorities of the existing rules based on the priority of the new Rule. It shift | ||
| * by one position the priority value of the rules having it >= the priority value of the new | ||
| * Rule. | ||
| * | ||
| * @param rules the already existing rule. | ||
| * @param newRule the new Rule to be added. | ||
| * @return a Set of rules having the priority fields updated. | ||
| */ | ||
| public static Set<TemplateRule> updatePriorities( | ||
| List<TemplateRule> rules, TemplateRule newRule) { | ||
| Set<TemplateRule> set = new HashSet<>(rules.size()); | ||
| int updatedPriority = newRule.getPriority(); | ||
| boolean newRuleAdded = false; | ||
| for (TemplateRule rule : rules) { | ||
| boolean isUpdating = rule.getRuleId().equals(newRule.getRuleId()); | ||
| int priority = rule.getPriority(); | ||
| if (priority == updatedPriority) { | ||
| if (!newRuleAdded) { | ||
| set.add(newRule); | ||
| newRuleAdded = true; | ||
| } | ||
| priority++; | ||
| if (!isUpdating) { | ||
| rule.setPriority(priority); | ||
| updatedPriority = priority; | ||
| } | ||
| } | ||
| if (!isUpdating) set.add(rule); | ||
| } | ||
| if (set.isEmpty() || !newRuleAdded) set.add(newRule); | ||
| return set; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| /* (c) 2021 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.featurestemplating.configuration; | ||
|
|
||
| /** | ||
| * This class provides the business logic to handle save, update and delete operation on a template. | ||
| */ | ||
| public class TemplateService { | ||
|
|
||
| private TemplateLoader loader; | ||
| private TemplateFileManager templateFileManager; | ||
| private TemplateInfoDAO templateInfoDAO; | ||
|
|
||
| public TemplateService() { | ||
| this.loader = TemplateLoader.get(); | ||
| this.templateFileManager = TemplateFileManager.get(); | ||
| this.templateInfoDAO = TemplateInfoDAO.get(); | ||
| } | ||
|
|
||
| /** | ||
| * Save or update a Template. Clean the cache from the previous template if necessary. | ||
| * | ||
| * @param templateInfo the Template Info to save or update. | ||
| * @param rawTemplate the raw Template to save or update. | ||
| */ | ||
| public void saveOrUpdate(TemplateInfo templateInfo, String rawTemplate) { | ||
| templateFileManager.saveTemplateFile(templateInfo, rawTemplate); | ||
| TemplateInfo current = templateInfoDAO.findById(templateInfo.getIdentifier()); | ||
| if (current != null && !current.getFullName().equals(templateInfo.getFullName())) { | ||
| if (templateFileManager.delete(current)) { | ||
| loader.removeAllWithIdentifier(templateInfo.getIdentifier()); | ||
| } | ||
| } | ||
| templateInfoDAO.saveOrUpdate(templateInfo); | ||
| } | ||
|
|
||
| /** | ||
| * Delete a Template and removes it from the cache. | ||
| * | ||
| * @param templateInfo the Template Info object to delete. | ||
| */ | ||
| public void delete(TemplateInfo templateInfo) { | ||
| templateFileManager.delete(templateInfo); | ||
| loader.removeAllWithIdentifier(templateInfo.getIdentifier()); | ||
| templateInfoDAO.delete(templateInfo); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package org.geoserver.featurestemplating.expressions; | ||
|
|
||
| import static org.geotools.filter.capability.FunctionNameImpl.parameter; | ||
|
|
||
| import org.geoserver.ows.Request; | ||
| import org.geotools.filter.capability.FunctionNameImpl; | ||
| import org.geotools.util.Converters; | ||
| import org.opengis.filter.capability.FunctionName; | ||
|
|
||
| /** Returns the value of request header with the name specified in the parameter. */ | ||
| public class HeaderFunction extends RequestFunction { | ||
|
|
||
| public static FunctionName NAME = | ||
| new FunctionNameImpl( | ||
| "header", parameter("result", String.class), parameter("name", String.class)); | ||
|
|
||
| public HeaderFunction() { | ||
| super(NAME); | ||
| } | ||
|
|
||
| @Override | ||
| protected Object evaluateInternal(Request request, Object object) { | ||
| String parameter = getParameters().get(0).evaluate(null, String.class); | ||
| Object value = request.getHttpRequest().getHeader(parameter); | ||
| return Converters.convert(value, String.class); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| /* (c) 2021 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.featurestemplating.expressions; | ||
|
|
||
| import static org.geotools.filter.capability.FunctionNameImpl.parameter; | ||
|
|
||
| import java.lang.reflect.Array; | ||
| import org.geotools.filter.FunctionExpressionImpl; | ||
| import org.geotools.filter.capability.FunctionNameImpl; | ||
| import org.opengis.feature.Attribute; | ||
| import org.opengis.filter.capability.FunctionName; | ||
|
|
||
| /** | ||
| * Allows extraction of a given item from an array (as it's hard to do with a xpath, since the array | ||
| * value is not quite the same as having an attribute with multiple repetitions) | ||
| */ | ||
| public class ItemFunction extends FunctionExpressionImpl { | ||
| public static FunctionName NAME = | ||
| new FunctionNameImpl( | ||
| "item", | ||
| Object.class, | ||
| parameter("array", Object.class), | ||
| parameter("idx", Integer.class)); | ||
|
|
||
| public ItemFunction() { | ||
| super(NAME); | ||
| } | ||
|
|
||
| @Override | ||
| public Object evaluate(Object feature) { | ||
| Object array = getExpression(0).evaluate(feature, Object.class); | ||
| if (array instanceof Attribute) { | ||
| array = ((Attribute) array).getValue(); | ||
| } | ||
| Integer idx = getExpression(1).evaluate(feature, Integer.class); | ||
|
|
||
| if (array == null) return null; | ||
| if (!array.getClass().isArray()) | ||
| throw new IllegalArgumentException("First argument is not an array"); | ||
|
|
||
| if (idx == null) return null; | ||
|
|
||
| return Array.get(array, idx); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| /* (c) 2021 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.featurestemplating.expressions; | ||
|
|
||
| import static org.geotools.filter.capability.FunctionNameImpl.parameter; | ||
|
|
||
| import org.geoserver.ows.Request; | ||
| import org.geotools.filter.capability.FunctionNameImpl; | ||
| import org.opengis.filter.capability.FunctionName; | ||
|
|
||
| /** Returns the Mime Type of the current {@link Request}. */ | ||
| public class MimeTypeFunction extends RequestFunction { | ||
|
|
||
| public static FunctionName NAME = | ||
| new FunctionNameImpl("mimeType", parameter("result", String.class)); | ||
|
|
||
| public MimeTypeFunction() { | ||
| super(NAME); | ||
| } | ||
|
|
||
| @Override | ||
| protected Object evaluateInternal(Request request, Object object) { | ||
| String outputFormat = request.getOutputFormat(); | ||
| if (outputFormat == null) { | ||
| outputFormat = request.getKvp() != null ? (String) request.getKvp().get("f") : null; | ||
| } | ||
| return outputFormat; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| package org.geoserver.featurestemplating.expressions; | ||
|
|
||
| import static org.geotools.filter.capability.FunctionNameImpl.parameter; | ||
|
|
||
| import org.geotools.filter.AttributeExpressionImpl; | ||
| import org.geotools.filter.FunctionExpressionImpl; | ||
| import org.geotools.filter.capability.FunctionNameImpl; | ||
| import org.opengis.filter.capability.FunctionName; | ||
| import org.opengis.filter.expression.ExpressionVisitor; | ||
| import org.opengis.filter.expression.PropertyName; | ||
| import org.xml.sax.helpers.NamespaceSupport; | ||
|
|
||
| public class PropertyPathFunction extends FunctionExpressionImpl implements PropertyName { | ||
|
|
||
| public static FunctionName NAME = | ||
| new FunctionNameImpl( | ||
| "propertyPath", | ||
| parameter("result", Object.class), | ||
| parameter("domainProperty", String.class)); | ||
|
|
||
| protected String propertyPath; | ||
|
|
||
| protected NamespaceSupport namespaceSupport; | ||
|
|
||
| public PropertyPathFunction(String propertyPath) { | ||
| super(NAME); | ||
| this.propertyPath = propertyPath; | ||
| } | ||
|
|
||
| public PropertyPathFunction() { | ||
| super(NAME); | ||
| } | ||
|
|
||
| @Override | ||
| public Object evaluate(Object object) { | ||
| String strPropertyPath = (String) getParameters().get(0).evaluate(object); | ||
| AttributeExpressionImpl attributeExpression = | ||
| new AttributeExpressionImpl(strPropertyPath, namespaceSupport); | ||
| return attributeExpression.evaluate(object); | ||
| } | ||
|
|
||
| @Override | ||
| public String getPropertyName() { | ||
| if (getParameters() != null && getParameters().size() > 0) | ||
| return getParameters().get(0).evaluate(null, String.class); | ||
| else return propertyPath; | ||
| } | ||
|
|
||
| public void setPropertyName(String propertyPath) { | ||
| this.propertyPath = propertyPath; | ||
| } | ||
|
|
||
| @Override | ||
| public NamespaceSupport getNamespaceContext() { | ||
| return namespaceSupport; | ||
| } | ||
|
|
||
| public void setNamespaceContext(NamespaceSupport namespaceContext) { | ||
| this.namespaceSupport = namespaceContext; | ||
| } | ||
|
|
||
| @Override | ||
| public Object accept(ExpressionVisitor visitor, Object extraData) { | ||
| // we explicitly handle the attribute extractor filter | ||
| return visitor.visit((PropertyName) this, extraData); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| /* (c) 2021 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.featurestemplating.expressions; | ||
|
|
||
| import java.util.logging.Logger; | ||
| import org.geoserver.ows.Dispatcher; | ||
| import org.geoserver.ows.Request; | ||
| import org.geotools.filter.FunctionExpressionImpl; | ||
| import org.geotools.util.logging.Logging; | ||
| import org.opengis.filter.capability.FunctionName; | ||
|
|
||
| /** | ||
| * Abstract function that evaluate against a {@link Request} object. Subclasses must implement | ||
| * evaluate internal. | ||
| */ | ||
| public abstract class RequestFunction extends FunctionExpressionImpl { | ||
|
|
||
| private static final Logger LOGGER = Logging.getLogger(RequestFunction.class); | ||
|
|
||
| protected RequestFunction(FunctionName functionName) { | ||
| super(functionName); | ||
| } | ||
|
|
||
| @Override | ||
| public Object evaluate(Object object) { | ||
| Request request = Dispatcher.REQUEST.get(); | ||
| if (request == null) { | ||
| LOGGER.info("Found a null Request object. Returning null"); | ||
| return null; | ||
| } else return evaluateInternal(request, object); | ||
| } | ||
|
|
||
| protected abstract Object evaluateInternal(Request request, Object object); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| /* (c) 2021 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.featurestemplating.expressions; | ||
|
|
||
| import static org.geotools.filter.capability.FunctionNameImpl.parameter; | ||
|
|
||
| import java.util.regex.Matcher; | ||
| import java.util.regex.Pattern; | ||
| import javax.servlet.http.HttpServletRequest; | ||
| import org.geoserver.ows.Request; | ||
| import org.geoserver.ows.util.ResponseUtils; | ||
| import org.geotools.filter.capability.FunctionNameImpl; | ||
| import org.opengis.filter.capability.FunctionName; | ||
|
|
||
| /** Check if the current {@link Request} matches the regex passed as an argument of the Function. */ | ||
| public class RequestMatchRegex extends RequestFunction { | ||
|
|
||
| public static FunctionName NAME = | ||
| new FunctionNameImpl( | ||
| "requestMatchRegex", | ||
| parameter("result", Boolean.class), | ||
| parameter("regex", String.class)); | ||
|
|
||
| public RequestMatchRegex() { | ||
| super(NAME); | ||
| } | ||
|
|
||
| @Override | ||
| protected Object evaluateInternal(Request request, Object object) { | ||
| String regex = getParameters().get(0).evaluate(null, String.class); | ||
| Pattern pattern = Pattern.compile(regex); | ||
| String url = getFullURL(request.getHttpRequest()); | ||
| Matcher matcher = pattern.matcher(url); | ||
| return matcher.matches(); | ||
| } | ||
|
|
||
| private String getFullURL(HttpServletRequest request) { | ||
| StringBuilder requestURL = new StringBuilder(ResponseUtils.baseURL(request)); | ||
| String pathInfo = request.getPathInfo(); | ||
| String queryString = request.getQueryString(); | ||
| if (pathInfo != null) { | ||
| if (pathInfo.startsWith("/")) pathInfo = pathInfo.substring(1); | ||
| requestURL.append(pathInfo); | ||
| } | ||
| if (queryString != null) { | ||
| requestURL.append("?").append(queryString); | ||
| } | ||
| return requestURL.toString(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /* (c) 2021 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.featurestemplating.expressions; | ||
|
|
||
| import static org.geotools.filter.capability.FunctionNameImpl.parameter; | ||
|
|
||
| import java.util.Map; | ||
| import javax.servlet.http.HttpServletRequest; | ||
| import org.geoserver.ows.Request; | ||
| import org.geotools.filter.capability.FunctionNameImpl; | ||
| import org.geotools.util.Converters; | ||
| import org.opengis.filter.capability.FunctionName; | ||
|
|
||
| /** Returns the value of the request parameter with the name specified in the function parameter. */ | ||
| public class RequestParameterFunction extends RequestFunction { | ||
|
|
||
| public static FunctionName NAME = | ||
| new FunctionNameImpl( | ||
| "requestParam", | ||
| parameter("result", String.class), | ||
| parameter("name", String.class)); | ||
|
|
||
| public RequestParameterFunction() { | ||
| super(NAME); | ||
| } | ||
|
|
||
| @Override | ||
| protected Object evaluateInternal(Request request, Object object) { | ||
| String parameter = getParameters().get(0).evaluate(null, String.class); | ||
| HttpServletRequest req = request.getHttpRequest(); | ||
| Object value = null; | ||
| if (req != null) { | ||
| value = req.getParameter(parameter); | ||
| } | ||
| Map<String, Object> rawKvp = request.getRawKvp(); | ||
| if (rawKvp != null && value == null) value = request.getRawKvp().get(parameter); | ||
|
|
||
| return Converters.convert(value, String.class); | ||
| } | ||
| } |