mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Added source-target mappings for Audit data
- Audit entry creation API no longer requires an application name - Inbound data is remapped according to the mappings in the audit XML files git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@16327 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -174,6 +174,7 @@ public class AuditApplication
|
||||
*/
|
||||
public void checkPath(String path)
|
||||
{
|
||||
checkPathFormat(path);
|
||||
if (path == null || path.length() == 0)
|
||||
{
|
||||
generateException(path, "Empty or null audit path");
|
||||
@@ -192,6 +193,26 @@ public class AuditApplication
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to check that a path is correct for this application instance
|
||||
*
|
||||
* @param path the path in format <b>/app-key/x/y/z</b>
|
||||
* @throws AuditModelException if the path is invalid
|
||||
*
|
||||
* @see #AUDIT_PATH_REGEX
|
||||
*/
|
||||
public static void checkPathFormat(String path)
|
||||
{
|
||||
if (path == null || path.length() == 0)
|
||||
{
|
||||
throw new AuditModelException("Empty or null audit path");
|
||||
}
|
||||
else if (!path.matches(AUDIT_PATH_REGEX))
|
||||
{
|
||||
throw new AuditModelException("An audit must match regular expression: " + AUDIT_PATH_REGEX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a path or part of a path into a single string which always starts with the
|
||||
* {@link #AUDIT_PATH_SEPARATOR}. This can be a relative path so need not always start with
|
||||
@@ -237,6 +258,33 @@ public class AuditApplication
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path the audit path for form <b>/abc/def</b>
|
||||
* @return the root key of form <b>abc</b>
|
||||
*
|
||||
* @see #AUDIT_ROOT_KEY_REGEX
|
||||
*/
|
||||
public static String getRootKey(String path)
|
||||
{
|
||||
if (!path.startsWith(AUDIT_PATH_SEPARATOR))
|
||||
{
|
||||
throw new AuditModelException(
|
||||
"The path must start with the path separator '" + AUDIT_PATH_SEPARATOR + "'");
|
||||
}
|
||||
String rootPath;
|
||||
int index = path.indexOf(AUDIT_PATH_SEPARATOR, 1);
|
||||
if (index > 0)
|
||||
{
|
||||
rootPath = path.substring(1, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
rootPath = path.substring(1);
|
||||
}
|
||||
// Done
|
||||
return rootPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all data extractors applicable to a given path and scope.
|
||||
*
|
||||
|
@@ -54,11 +54,14 @@ import org.alfresco.repo.audit.model._3.Audit;
|
||||
import org.alfresco.repo.audit.model._3.DataExtractors;
|
||||
import org.alfresco.repo.audit.model._3.DataGenerators;
|
||||
import org.alfresco.repo.audit.model._3.ObjectFactory;
|
||||
import org.alfresco.repo.audit.model._3.PathMap;
|
||||
import org.alfresco.repo.audit.model._3.PathMappings;
|
||||
import org.alfresco.repo.domain.audit.AuditDAO;
|
||||
import org.alfresco.repo.domain.audit.AuditDAO.AuditApplicationInfo;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.PathMapper;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.alfresco.util.registry.NamedObjectRegistry;
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -92,6 +95,14 @@ public class AuditModelRegistry
|
||||
|
||||
private final Set<URL> auditModelUrls;
|
||||
private final List<Audit> auditModels;
|
||||
/**
|
||||
* Used to lookup path translations
|
||||
*/
|
||||
private PathMapper auditPathMapper;
|
||||
/**
|
||||
* Used to lookup the audit application java hierarchy
|
||||
*/
|
||||
private final Map<String, AuditApplication> auditApplicationsByKey;
|
||||
/**
|
||||
* Used to lookup the audit application java hierarchy
|
||||
*/
|
||||
@@ -110,6 +121,8 @@ public class AuditModelRegistry
|
||||
|
||||
auditModelUrls = new HashSet<URL>(7);
|
||||
auditModels = new ArrayList<Audit>(7);
|
||||
auditPathMapper = new PathMapper();
|
||||
auditApplicationsByKey = new HashMap<String, AuditApplication>(7);
|
||||
auditApplicationsByName = new HashMap<String, AuditApplication>(7);
|
||||
}
|
||||
|
||||
@@ -204,6 +217,7 @@ public class AuditModelRegistry
|
||||
private void clearCaches()
|
||||
{
|
||||
auditModels.clear();
|
||||
auditApplicationsByKey.clear();
|
||||
auditApplicationsByName.clear();
|
||||
}
|
||||
|
||||
@@ -227,11 +241,11 @@ public class AuditModelRegistry
|
||||
Set<URL> auditModelUrlsInner = new HashSet<URL>(auditModelUrls);
|
||||
for (URL auditModelUrl : auditModelUrlsInner)
|
||||
{
|
||||
Audit audit = AuditModelRegistry.unmarshallModel(auditModelUrl);
|
||||
// That worked, so now get an input stream and write the model
|
||||
Long auditModelId = auditDAO.getOrCreateAuditModel(auditModelUrl).getFirst();
|
||||
try
|
||||
{
|
||||
Audit audit = AuditModelRegistry.unmarshallModel(auditModelUrl);
|
||||
// That worked, so now get an input stream and write the model
|
||||
Long auditModelId = auditDAO.getOrCreateAuditModel(auditModelUrl).getFirst();
|
||||
// Now cache it (eagerly)
|
||||
cacheAuditElements(auditModelId, audit);
|
||||
}
|
||||
@@ -258,7 +272,9 @@ public class AuditModelRegistry
|
||||
clearCaches();
|
||||
try
|
||||
{
|
||||
auditPathMapper = new PathMapper();
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(loadModelsCallback, false, true);
|
||||
auditPathMapper.lock();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -266,13 +282,32 @@ public class AuditModelRegistry
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the application model for the given root key (as defined on the application)
|
||||
*
|
||||
* @param key the key defined on the application
|
||||
* @return the java model (<tt>null</tt> if not found)
|
||||
*/
|
||||
public AuditApplication getAuditApplicationByKey(String key)
|
||||
{
|
||||
readLock.lock();
|
||||
try
|
||||
{
|
||||
return auditApplicationsByKey.get(key);
|
||||
}
|
||||
finally
|
||||
{
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the application model for the given application name
|
||||
*
|
||||
* @param applicationName the name of the audited application
|
||||
* @return the java model (<tt>null</tt> if not found)
|
||||
*/
|
||||
public AuditApplication getAuditApplication(String applicationName)
|
||||
public AuditApplication getAuditApplicationByName(String applicationName)
|
||||
{
|
||||
readLock.lock();
|
||||
try
|
||||
@@ -285,6 +320,15 @@ public class AuditModelRegistry
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the
|
||||
* @return
|
||||
*/
|
||||
public PathMapper getAuditPathMapper()
|
||||
{
|
||||
return auditPathMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshalls the Audit model from the URL.
|
||||
*
|
||||
@@ -331,7 +375,7 @@ public class AuditModelRegistry
|
||||
" Location: Line " + locator.getLineNumber() + " column " + locator.getColumnNumber() + "\n" +
|
||||
" Error: " + ve.getMessage());
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -486,10 +530,18 @@ public class AuditModelRegistry
|
||||
List<Application> applications = audit.getApplication();
|
||||
for (Application application : applications)
|
||||
{
|
||||
String key = application.getKey();
|
||||
if (auditApplicationsByKey.containsKey(key))
|
||||
{
|
||||
throw new AuditModelException(
|
||||
"Audit application key '" + key + "' is used by: " + auditApplicationsByKey.get(key));
|
||||
}
|
||||
|
||||
String name = application.getName();
|
||||
if (auditApplicationsByName.containsKey(name))
|
||||
{
|
||||
throw new AuditModelException("Audit application '" + name + "' has already been defined.");
|
||||
throw new AuditModelException(
|
||||
"Audit application '" + name + "' is used by: " + auditApplicationsByName.get(name));
|
||||
}
|
||||
|
||||
// Get the ID of the application
|
||||
@@ -511,8 +563,29 @@ public class AuditModelRegistry
|
||||
appInfo.getId(),
|
||||
appInfo.getDisabledPathsId());
|
||||
auditApplicationsByName.put(name, wrapperApp);
|
||||
auditApplicationsByKey.put(key, wrapperApp);
|
||||
}
|
||||
// Pull out all the audit path maps
|
||||
buildAuditPathMap(audit);
|
||||
// Store the model itself
|
||||
auditModels.add(audit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the reverse lookup maps for quick conversion of data to target maps
|
||||
*/
|
||||
private void buildAuditPathMap(Audit audit)
|
||||
{
|
||||
PathMappings pathMappings = audit.getPathMappings();
|
||||
if (pathMappings == null)
|
||||
{
|
||||
pathMappings = objectFactory.createPathMappings();
|
||||
}
|
||||
for (PathMap pathMap : pathMappings.getPathMap())
|
||||
{
|
||||
String sourcePath = pathMap.getSource();
|
||||
String targetPath = pathMap.getTarget();
|
||||
auditPathMapper.addPathMap(sourcePath, targetPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,12 @@
|
||||
|
||||
package org.alfresco.repo.audit.model._3;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
|
||||
@@ -16,6 +19,9 @@ import javax.xml.bind.annotation.XmlType;
|
||||
* <complexType name="Application">
|
||||
* <complexContent>
|
||||
* <extension base="{http://www.alfresco.org/repo/audit/model/3.2}AuditPath">
|
||||
* <sequence>
|
||||
* <element name="PathMappings" type="{http://www.alfresco.org/repo/audit/model/3.2}PathMappings" maxOccurs="unbounded" minOccurs="0"/>
|
||||
* </sequence>
|
||||
* <attribute name="name" use="required" type="{http://www.alfresco.org/repo/audit/model/3.2}NameAttribute" />
|
||||
* </extension>
|
||||
* </complexContent>
|
||||
@@ -25,14 +31,47 @@ import javax.xml.bind.annotation.XmlType;
|
||||
*
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "Application")
|
||||
@XmlType(name = "Application", propOrder = {
|
||||
"pathMappings"
|
||||
})
|
||||
public class Application
|
||||
extends AuditPath
|
||||
{
|
||||
|
||||
@XmlElement(name = "PathMappings")
|
||||
protected List<PathMappings> pathMappings;
|
||||
@XmlAttribute(required = true)
|
||||
protected String name;
|
||||
|
||||
/**
|
||||
* Gets the value of the pathMappings property.
|
||||
*
|
||||
* <p>
|
||||
* This accessor method returns a reference to the live list,
|
||||
* not a snapshot. Therefore any modification you make to the
|
||||
* returned list will be present inside the JAXB object.
|
||||
* This is why there is not a <CODE>set</CODE> method for the pathMappings property.
|
||||
*
|
||||
* <p>
|
||||
* For example, to add a new item, do as follows:
|
||||
* <pre>
|
||||
* getPathMappings().add(newItem);
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Objects of the following type(s) are allowed in the list
|
||||
* {@link PathMappings }
|
||||
*
|
||||
*
|
||||
*/
|
||||
public List<PathMappings> getPathMappings() {
|
||||
if (pathMappings == null) {
|
||||
pathMappings = new ArrayList<PathMappings>();
|
||||
}
|
||||
return this.pathMappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the name property.
|
||||
*
|
||||
|
@@ -21,6 +21,7 @@ import javax.xml.bind.annotation.XmlType;
|
||||
* <sequence>
|
||||
* <element name="DataExtractors" type="{http://www.alfresco.org/repo/audit/model/3.2}DataExtractors" minOccurs="0"/>
|
||||
* <element name="DataGenerators" type="{http://www.alfresco.org/repo/audit/model/3.2}DataGenerators" minOccurs="0"/>
|
||||
* <element name="PathMappings" type="{http://www.alfresco.org/repo/audit/model/3.2}PathMappings" minOccurs="0"/>
|
||||
* <element name="Application" type="{http://www.alfresco.org/repo/audit/model/3.2}Application" maxOccurs="unbounded" minOccurs="0"/>
|
||||
* </sequence>
|
||||
* </restriction>
|
||||
@@ -34,6 +35,7 @@ import javax.xml.bind.annotation.XmlType;
|
||||
@XmlType(name = "Audit", propOrder = {
|
||||
"dataExtractors",
|
||||
"dataGenerators",
|
||||
"pathMappings",
|
||||
"application"
|
||||
})
|
||||
public class Audit {
|
||||
@@ -42,6 +44,8 @@ public class Audit {
|
||||
protected DataExtractors dataExtractors;
|
||||
@XmlElement(name = "DataGenerators")
|
||||
protected DataGenerators dataGenerators;
|
||||
@XmlElement(name = "PathMappings")
|
||||
protected PathMappings pathMappings;
|
||||
@XmlElement(name = "Application")
|
||||
protected List<Application> application;
|
||||
|
||||
@@ -93,6 +97,30 @@ public class Audit {
|
||||
this.dataGenerators = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the pathMappings property.
|
||||
*
|
||||
* @return
|
||||
* possible object is
|
||||
* {@link PathMappings }
|
||||
*
|
||||
*/
|
||||
public PathMappings getPathMappings() {
|
||||
return pathMappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the pathMappings property.
|
||||
*
|
||||
* @param value
|
||||
* allowed object is
|
||||
* {@link PathMappings }
|
||||
*
|
||||
*/
|
||||
public void setPathMappings(PathMappings value) {
|
||||
this.pathMappings = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the application property.
|
||||
*
|
||||
|
@@ -33,14 +33,6 @@ public class ObjectFactory {
|
||||
public ObjectFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link RecordValue }
|
||||
*
|
||||
*/
|
||||
public RecordValue createRecordValue() {
|
||||
return new RecordValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link DataExtractor }
|
||||
*
|
||||
@@ -57,6 +49,14 @@ public class ObjectFactory {
|
||||
return new Audit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link DataExtractors }
|
||||
*
|
||||
*/
|
||||
public DataExtractors createDataExtractors() {
|
||||
return new DataExtractors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link AuditPath }
|
||||
*
|
||||
@@ -66,19 +66,11 @@ public class ObjectFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link DataGenerator }
|
||||
* Create an instance of {@link PathMap }
|
||||
*
|
||||
*/
|
||||
public DataGenerator createDataGenerator() {
|
||||
return new DataGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link DataGenerators }
|
||||
*
|
||||
*/
|
||||
public DataGenerators createDataGenerators() {
|
||||
return new DataGenerators();
|
||||
public PathMap createPathMap() {
|
||||
return new PathMap();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,6 +81,22 @@ public class ObjectFactory {
|
||||
return new KeyedAuditDefinition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link PathMappings }
|
||||
*
|
||||
*/
|
||||
public PathMappings createPathMappings() {
|
||||
return new PathMappings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link DataGenerator }
|
||||
*
|
||||
*/
|
||||
public DataGenerator createDataGenerator() {
|
||||
return new DataGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link Application }
|
||||
*
|
||||
@@ -97,6 +105,14 @@ public class ObjectFactory {
|
||||
return new Application();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link DataGenerators }
|
||||
*
|
||||
*/
|
||||
public DataGenerators createDataGenerators() {
|
||||
return new DataGenerators();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link GenerateValue }
|
||||
*
|
||||
@@ -106,11 +122,11 @@ public class ObjectFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link DataExtractors }
|
||||
* Create an instance of {@link RecordValue }
|
||||
*
|
||||
*/
|
||||
public DataExtractors createDataExtractors() {
|
||||
return new DataExtractors();
|
||||
public RecordValue createRecordValue() {
|
||||
return new RecordValue();
|
||||
}
|
||||
|
||||
/**
|
||||
|
85
source/java/org/alfresco/repo/audit/model/_3/PathMap.java
Normal file
85
source/java/org/alfresco/repo/audit/model/_3/PathMap.java
Normal file
@@ -0,0 +1,85 @@
|
||||
|
||||
package org.alfresco.repo.audit.model._3;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Java class for PathMap complex type.
|
||||
*
|
||||
* <p>The following schema fragment specifies the expected content contained within this class.
|
||||
*
|
||||
* <pre>
|
||||
* <complexType name="PathMap">
|
||||
* <complexContent>
|
||||
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
|
||||
* <attribute name="source" use="required" type="{http://www.alfresco.org/repo/audit/model/3.2}PathAttribute" />
|
||||
* <attribute name="target" use="required" type="{http://www.alfresco.org/repo/audit/model/3.2}PathAttribute" />
|
||||
* </restriction>
|
||||
* </complexContent>
|
||||
* </complexType>
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "PathMap")
|
||||
public class PathMap {
|
||||
|
||||
@XmlAttribute(required = true)
|
||||
protected String source;
|
||||
@XmlAttribute(required = true)
|
||||
protected String target;
|
||||
|
||||
/**
|
||||
* Gets the value of the source property.
|
||||
*
|
||||
* @return
|
||||
* possible object is
|
||||
* {@link String }
|
||||
*
|
||||
*/
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the source property.
|
||||
*
|
||||
* @param value
|
||||
* allowed object is
|
||||
* {@link String }
|
||||
*
|
||||
*/
|
||||
public void setSource(String value) {
|
||||
this.source = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the target property.
|
||||
*
|
||||
* @return
|
||||
* possible object is
|
||||
* {@link String }
|
||||
*
|
||||
*/
|
||||
public String getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the target property.
|
||||
*
|
||||
* @param value
|
||||
* allowed object is
|
||||
* {@link String }
|
||||
*
|
||||
*/
|
||||
public void setTarget(String value) {
|
||||
this.target = value;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
|
||||
package org.alfresco.repo.audit.model._3;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Java class for PathMappings complex type.
|
||||
*
|
||||
* <p>The following schema fragment specifies the expected content contained within this class.
|
||||
*
|
||||
* <pre>
|
||||
* <complexType name="PathMappings">
|
||||
* <complexContent>
|
||||
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
|
||||
* <sequence>
|
||||
* <element name="PathMap" type="{http://www.alfresco.org/repo/audit/model/3.2}PathMap" maxOccurs="unbounded" minOccurs="0"/>
|
||||
* </sequence>
|
||||
* </restriction>
|
||||
* </complexContent>
|
||||
* </complexType>
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "PathMappings", propOrder = {
|
||||
"pathMap"
|
||||
})
|
||||
public class PathMappings {
|
||||
|
||||
@XmlElement(name = "PathMap")
|
||||
protected List<PathMap> pathMap;
|
||||
|
||||
/**
|
||||
* Gets the value of the pathMap property.
|
||||
*
|
||||
* <p>
|
||||
* This accessor method returns a reference to the live list,
|
||||
* not a snapshot. Therefore any modification you make to the
|
||||
* returned list will be present inside the JAXB object.
|
||||
* This is why there is not a <CODE>set</CODE> method for the pathMap property.
|
||||
*
|
||||
* <p>
|
||||
* For example, to add a new item, do as follows:
|
||||
* <pre>
|
||||
* getPathMap().add(newItem);
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Objects of the following type(s) are allowed in the list
|
||||
* {@link PathMap }
|
||||
*
|
||||
*
|
||||
*/
|
||||
public List<PathMap> getPathMap() {
|
||||
if (pathMap == null) {
|
||||
pathMap = new ArrayList<PathMap>();
|
||||
}
|
||||
return this.pathMap;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user