83 |
83 |
** - absolute name
|
84 |
84 |
** - PROPATH
|
85 |
85 |
** - direct match.
|
|
86 |
** 039 CA 20180504 Added support for folder aliasing: these are specified via the
|
|
87 |
** "path-aliases" per-server directory node, and provide a mapping of
|
|
88 |
** alias folder name to their 'source' counterpart. Also, added support
|
|
89 |
** to identify the target source program for a RUN program.r statement.
|
86 |
90 |
*/
|
87 |
91 |
|
88 |
92 |
/*
|
... | ... | |
142 |
146 |
|
143 |
147 |
import java.io.*;
|
144 |
148 |
import java.util.*;
|
|
149 |
import java.util.function.*;
|
|
150 |
import java.util.logging.*;
|
145 |
151 |
import java.net.*;
|
146 |
152 |
import org.w3c.dom.*;
|
147 |
153 |
import com.goldencode.util.*;
|
|
154 |
import com.goldencode.p2j.directory.*;
|
148 |
155 |
import com.goldencode.p2j.library.*;
|
149 |
156 |
import com.goldencode.p2j.security.*;
|
150 |
157 |
|
... | ... | |
184 |
191 |
*/
|
185 |
192 |
public class SourceNameMapper
|
186 |
193 |
{
|
|
194 |
/** logger */
|
|
195 |
private static final Logger LOG = LogHelper.getLogger(SourceNameMapper.class.getName());
|
|
196 |
|
187 |
197 |
/** Context-local data. */
|
188 |
198 |
private static final ContextLocal<WorkArea> local = new ContextLocal<WorkArea>()
|
189 |
199 |
{
|
... | ... | |
227 |
237 |
/** Named events attribute. */
|
228 |
238 |
private static final String PUBLISHED_EVENTS = "published-events";
|
229 |
239 |
|
|
240 |
/** Map of aliases (r-code folders) to their source folders (conversion folders). */
|
|
241 |
private static Map<String, String> pathAliases = null;
|
|
242 |
|
230 |
243 |
/** Progress procedure file name to Java class name map table. */
|
231 |
244 |
private static volatile Map<String, ExternalProgram> p2jMap = null;
|
232 |
245 |
|
|
246 |
/**
|
|
247 |
* Map of program names without extension to the programs which can be resolved with this name.
|
|
248 |
*/
|
|
249 |
private static volatile Map<String, List<ExternalProgram>> p2jNonExtMap = null;
|
|
250 |
|
233 |
251 |
/** Progress Java class name to procedure file name map table. */
|
234 |
252 |
private static Map<String, ExternalProgram> j2pMap = null;
|
235 |
253 |
|
... | ... | |
851 |
869 |
|
852 |
870 |
return extProg.search(iename, func);
|
853 |
871 |
}
|
854 |
|
|
|
872 |
|
855 |
873 |
/**
|
856 |
874 |
* Convert a Progress file name into Java class name. The input name can be an absolute file
|
857 |
875 |
* name, a file name relative to current location or a file name relative to some directory
|
... | ... | |
891 |
909 |
* "c:/path/to/project/stcases/uast/rundir/absolute/program.p"
|
892 |
910 |
* "rundir/../../uast/rundir/relative/test11.p."
|
893 |
911 |
* "../uast/rundir/relative/test5.p"
|
894 |
|
*
|
|
912 |
* <p>
|
|
913 |
* If no program name is explicitly found with a <code>.r</code> extension, R-code program
|
|
914 |
* names are looked up using the {@link #p2jNonExtMap} (the program name without the extension).
|
|
915 |
* <p>
|
|
916 |
* If {@link #pathAliases} is specified, and the program can be found (as it is or after
|
|
917 |
* removing the <code>.r</code> extension) in the propath, the lookup falls back to resolving
|
|
918 |
* the aliased folders in the program name, via {@link #resolvePathAliases}.
|
|
919 |
*
|
|
920 |
*
|
895 |
921 |
* @param legacyProgName
|
896 |
922 |
* Progress file name.
|
897 |
923 |
*
|
... | ... | |
901 |
927 |
*/
|
902 |
928 |
private static String[] convertName(String legacyProgName)
|
903 |
929 |
{
|
|
930 |
Function<String, ExternalProgram> lookupDirectName;
|
|
931 |
Function<String, ExternalProgram> lookupCompiledRcode;
|
|
932 |
Function<String, ExternalProgram> lookupAliasedDirectName;
|
|
933 |
Function<String, ExternalProgram> lookupAliasedCompiledRcode;
|
|
934 |
|
|
935 |
Function<String, Boolean> isRcode =
|
|
936 |
(pname -> pname.endsWith(".r") || (!caseSens && pname.endsWith(".R")));
|
|
937 |
boolean legacyRcode = isRcode.apply(legacyProgName);
|
|
938 |
boolean hasAliases = pathAliases != null;
|
|
939 |
String msg = "Found first match %s for program %s, and additional %s";
|
|
940 |
|
|
941 |
lookupDirectName = (pname -> p2jMap.get(pname));
|
|
942 |
lookupCompiledRcode = !legacyRcode ? null : (pname ->
|
|
943 |
{
|
|
944 |
if (!isRcode.apply(pname))
|
|
945 |
{
|
|
946 |
return null;
|
|
947 |
}
|
|
948 |
|
|
949 |
pname = pname.substring(0, pname.length() - ".r".length());
|
|
950 |
|
|
951 |
ExternalProgram extProg = null;
|
|
952 |
|
|
953 |
// we could use a trie, but keeping a match of names without extension to the full
|
|
954 |
// program names (with extension) is easier
|
|
955 |
List<ExternalProgram> progs = p2jNonExtMap.get(pname);
|
|
956 |
|
|
957 |
if (progs != null && !progs.isEmpty())
|
|
958 |
{
|
|
959 |
extProg = progs.get(0);
|
|
960 |
|
|
961 |
if (LOG.isLoggable(Level.WARNING))
|
|
962 |
{
|
|
963 |
for (int i = 1; i < progs.size(); i++)
|
|
964 |
{
|
|
965 |
LOG.log(Level.WARNING, String.format(msg,
|
|
966 |
extProg.pname,
|
|
967 |
legacyProgName,
|
|
968 |
progs.get(i).pname));
|
|
969 |
}
|
|
970 |
}
|
|
971 |
}
|
|
972 |
|
|
973 |
return extProg;
|
|
974 |
});
|
|
975 |
lookupAliasedDirectName = !hasAliases ? null : (pname ->
|
|
976 |
{
|
|
977 |
String[] aliases = resolvePathAliases(pname);
|
|
978 |
|
|
979 |
ExternalProgram extProg = null;
|
|
980 |
|
|
981 |
for (String aliasName : aliases)
|
|
982 |
{
|
|
983 |
ExternalProgram prog = lookupDirectName.apply(aliasName);
|
|
984 |
|
|
985 |
if (prog != null)
|
|
986 |
{
|
|
987 |
if (extProg != null)
|
|
988 |
{
|
|
989 |
if (LOG.isLoggable(Level.WARNING))
|
|
990 |
{
|
|
991 |
LOG.log(Level.WARNING, String.format(msg,
|
|
992 |
extProg.pname,
|
|
993 |
legacyProgName,
|
|
994 |
prog.pname));
|
|
995 |
}
|
|
996 |
}
|
|
997 |
else
|
|
998 |
{
|
|
999 |
extProg = prog;
|
|
1000 |
}
|
|
1001 |
}
|
|
1002 |
}
|
|
1003 |
|
|
1004 |
return extProg;
|
|
1005 |
});
|
|
1006 |
lookupAliasedCompiledRcode = !legacyRcode || !hasAliases ? null : (pname ->
|
|
1007 |
{
|
|
1008 |
if (!isRcode.apply(pname))
|
|
1009 |
{
|
|
1010 |
return null;
|
|
1011 |
}
|
|
1012 |
|
|
1013 |
String[] aliases = resolvePathAliases(pname);
|
|
1014 |
|
|
1015 |
ExternalProgram extProg = null;
|
|
1016 |
|
|
1017 |
for (String aliasName : aliases)
|
|
1018 |
{
|
|
1019 |
ExternalProgram prog = lookupCompiledRcode.apply(aliasName);
|
|
1020 |
|
|
1021 |
if (prog != null)
|
|
1022 |
{
|
|
1023 |
if (extProg != null)
|
|
1024 |
{
|
|
1025 |
LOG.log(Level.WARNING, String.format(msg,
|
|
1026 |
extProg.pname,
|
|
1027 |
legacyProgName,
|
|
1028 |
prog.pname));
|
|
1029 |
}
|
|
1030 |
else
|
|
1031 |
{
|
|
1032 |
extProg = prog;
|
|
1033 |
}
|
|
1034 |
}
|
|
1035 |
}
|
|
1036 |
|
|
1037 |
return extProg;
|
|
1038 |
});
|
|
1039 |
|
|
1040 |
@SuppressWarnings("unchecked")
|
|
1041 |
Function<String, ExternalProgram>[] lookups = new Function[]
|
|
1042 |
{
|
|
1043 |
lookupDirectName,
|
|
1044 |
lookupCompiledRcode,
|
|
1045 |
lookupAliasedDirectName,
|
|
1046 |
lookupAliasedCompiledRcode,
|
|
1047 |
};
|
|
1048 |
|
|
1049 |
for (Function<String, ExternalProgram> lookup : lookups)
|
|
1050 |
{
|
|
1051 |
if (lookup == null)
|
|
1052 |
{
|
|
1053 |
continue;
|
|
1054 |
}
|
|
1055 |
|
|
1056 |
String[] res = convertName(legacyProgName, lookup);
|
|
1057 |
if (res != null)
|
|
1058 |
{
|
|
1059 |
return res;
|
|
1060 |
}
|
|
1061 |
}
|
|
1062 |
|
|
1063 |
return null;
|
|
1064 |
}
|
|
1065 |
|
|
1066 |
/**
|
|
1067 |
* Convert a Progress file name into Java class name. The input name can be an absolute file
|
|
1068 |
* name, a file name relative to current location or a file name relative to some directory
|
|
1069 |
* listed in the PROPATH variable. Before processing, the input file name is processed by
|
|
1070 |
* {@link #canonicalize}.
|
|
1071 |
* <p>
|
|
1072 |
* A simple lookup is tried first. If this fails direct check for absolute path if attempted
|
|
1073 |
* using one of the roots from {@code legacyRoots}. In multiple project scenario, the project
|
|
1074 |
* name is also added as a path token in order to match the structure from
|
|
1075 |
* {@code name_map.xml}.
|
|
1076 |
* <p>
|
|
1077 |
* If this mapping does not exist then each path segment of the PROPATH is tried in order.
|
|
1078 |
* The given program name is check to see if it is prefixed with the PROPATH segment, if so
|
|
1079 |
* that prefix is removed and a lookup is done with this name. The first match found ends the
|
|
1080 |
* search.
|
|
1081 |
* <p>
|
|
1082 |
* Absolute paths are converted into relative paths based on the existence of a matching path
|
|
1083 |
* in the legacy PROPATH (see {@link EnvironmentOps#getLegacySearchPath}). To handle additional
|
|
1084 |
* absolute paths, one must add the appropriate path prefix to the legacy PROPATH and it will
|
|
1085 |
* be removed here.
|
|
1086 |
* <p>
|
|
1087 |
* This same PROPATH processing is how names relative to the current directory can be handled.
|
|
1088 |
* This is the only way to handle that case since the current directory in the source system is
|
|
1089 |
* runtime information that is not available here.
|
|
1090 |
* <p>
|
|
1091 |
* The legacy PROPATH is the key configuration tool by which one specifies to this method how
|
|
1092 |
* to remove absolute and current directory relative prefixes. This means that the legacy
|
|
1093 |
* PROPATH that is used here may need to be different than the PROPATH on the original system.
|
|
1094 |
* <p>
|
|
1095 |
* As a fallback, if no mapping can be found and if the given {@code progName} is a fully
|
|
1096 |
* qualified Java class name which references a valid Java class, then the {@code progName} is
|
|
1097 |
* returned unchanged.
|
|
1098 |
* <p>
|
|
1099 |
* In Windows it can verify the absolute or relative program name is in current PROPATH
|
|
1100 |
* directory set. This allows to properly map the following program names used in
|
|
1101 |
* PROGRESS RUN statement:
|
|
1102 |
* "c:/path/to/project/stcases/uast/rundir/absolute/program.p"
|
|
1103 |
* "rundir/../../uast/rundir/relative/test11.p."
|
|
1104 |
* "../uast/rundir/relative/test5.p"
|
|
1105 |
*
|
|
1106 |
* @param legacyProgName
|
|
1107 |
* Progress file name.
|
|
1108 |
* @param lookup
|
|
1109 |
* The lookup function used to resolve a program name to a {@link ExternalProgram}.
|
|
1110 |
*
|
|
1111 |
* @return A {@code String} array with two components: Java class name for the given Progress
|
|
1112 |
* file name as first element and the legacy procedure name in second element of the
|
|
1113 |
* array. If no appropriate mapping is present {@code null} is returned.
|
|
1114 |
*/
|
|
1115 |
private static String[] convertName(String legacyProgName,
|
|
1116 |
Function<String, ExternalProgram> lookup)
|
|
1117 |
{
|
904 |
1118 |
initMappingData();
|
905 |
1119 |
|
906 |
1120 |
// get the original propath and split it into pieces
|
... | ... | |
965 |
1179 |
|
966 |
1180 |
// regardless if there are '..' up paths or not, we must check the relName against
|
967 |
1181 |
// our dictionary - don't use the propath here.
|
968 |
|
extProg = p2jMap.get(relName);
|
|
1182 |
extProg = lookup.apply(relName);
|
969 |
1183 |
|
970 |
1184 |
if (extProg == null)
|
971 |
1185 |
{
|
... | ... | |
974 |
1188 |
String token = Utils.getProjectToken();
|
975 |
1189 |
if (token != null)
|
976 |
1190 |
{
|
977 |
|
extProg = p2jMap.get(token + fileSep + relName);
|
|
1191 |
extProg = lookup.apply(token + fileSep + relName);
|
978 |
1192 |
}
|
979 |
1193 |
}
|
980 |
1194 |
|
... | ... | |
1022 |
1236 |
// absolute folder.
|
1023 |
1237 |
|
1024 |
1238 |
// check for a match
|
1025 |
|
extProg = p2jMap.get(canonicalize(relName));
|
|
1239 |
extProg = lookup.apply(canonicalize(relName));
|
1026 |
1240 |
|
1027 |
1241 |
if (extProg != null)
|
1028 |
1242 |
{
|
... | ... | |
1038 |
1252 |
// case 3. check the simple form of the name - in this case, just check for an exact match
|
1039 |
1253 |
if (extProg == null)
|
1040 |
1254 |
{
|
1041 |
|
extProg = p2jMap.get(canonicalize(progName));
|
|
1255 |
extProg = lookup.apply(canonicalize(progName));
|
1042 |
1256 |
}
|
1043 |
1257 |
|
1044 |
1258 |
// case 4. fallback processing
|
... | ... | |
1217 |
1431 |
}
|
1218 |
1432 |
|
1219 |
1433 |
/**
|
|
1434 |
* Given a program name, replace all folder names which have an associated 'source' folder,
|
|
1435 |
* and return the new path.
|
|
1436 |
*
|
|
1437 |
* @param pname
|
|
1438 |
* The legacy program name.
|
|
1439 |
*
|
|
1440 |
* @return The resolved program name.
|
|
1441 |
*/
|
|
1442 |
private static String[] resolvePathAliases(String pname)
|
|
1443 |
{
|
|
1444 |
String[] entries = pname.split(fileSep);
|
|
1445 |
|
|
1446 |
// for now, we replace all found aliases with their source folders; if needed, we can
|
|
1447 |
// compute all power sets made from pathAliases (will be 2^(pathAliases.size()) subsets)
|
|
1448 |
|
|
1449 |
StringBuilder sb = new StringBuilder();
|
|
1450 |
for (int i = 0; i < entries.length - 1; i++)
|
|
1451 |
{
|
|
1452 |
String entry = entries[i];
|
|
1453 |
|
|
1454 |
// if this entry is an alias, get its source
|
|
1455 |
String source = pathAliases.get(entry);
|
|
1456 |
|
|
1457 |
sb.append(source == null ? entry : source).append(fileSep);
|
|
1458 |
}
|
|
1459 |
sb.append(entries[entries.length - 1]);
|
|
1460 |
|
|
1461 |
return new String[] { sb.toString() };
|
|
1462 |
}
|
|
1463 |
|
|
1464 |
/**
|
|
1465 |
* Load the path aliases from the directory, into the {@link #pathAliases} map. This map
|
|
1466 |
* contains as key the 'aliased' name which is an alias for a 'source' folder.
|
|
1467 |
* <p>
|
|
1468 |
* The directory contains these mappings in the <code>/server/default/path-aliases</code> or
|
|
1469 |
* <code>/server/<server-id>/path-aliases</code> node. The child nodes have as class
|
|
1470 |
* <code>path-alias</code> and this structure:
|
|
1471 |
* <code>
|
|
1472 |
* <node class="path-alias" name="alias1">
|
|
1473 |
* <br>
|
|
1474 |
* <node-attribute name="source" value="src1"/>
|
|
1475 |
* <br>
|
|
1476 |
* <node-attribute name="alias" value="obj1"/>
|
|
1477 |
* <br>
|
|
1478 |
* </node>
|
|
1479 |
* </code>
|
|
1480 |
* <p>
|
|
1481 |
* The <code>alias1</code> name is just an unique ID for each child.
|
|
1482 |
*/
|
|
1483 |
private static void loadPathAliases()
|
|
1484 |
{
|
|
1485 |
DirectoryService ds = DirectoryService.getInstance();
|
|
1486 |
|
|
1487 |
if (!ds.bind())
|
|
1488 |
throw new RuntimeException("directory bind failed");
|
|
1489 |
|
|
1490 |
try
|
|
1491 |
{
|
|
1492 |
String path = Utils.findDirectoryNodePath(ds, "path-aliases", "container", false);
|
|
1493 |
if (path == null)
|
|
1494 |
{
|
|
1495 |
return;
|
|
1496 |
}
|
|
1497 |
|
|
1498 |
String[] aliasPaths = ds.enumerateNodes(path);
|
|
1499 |
if (aliasPaths.length == 0)
|
|
1500 |
{
|
|
1501 |
return;
|
|
1502 |
}
|
|
1503 |
|
|
1504 |
pathAliases = new HashMap<>();
|
|
1505 |
for (String aliasPath : aliasPaths)
|
|
1506 |
{
|
|
1507 |
aliasPath = path + "/" + aliasPath;
|
|
1508 |
String source = ds.getNodeString(aliasPath, "source");
|
|
1509 |
String alias = ds.getNodeString(aliasPath, "alias");
|
|
1510 |
|
|
1511 |
if (!caseSens)
|
|
1512 |
{
|
|
1513 |
source = source.toLowerCase();
|
|
1514 |
alias = alias.toLowerCase();
|
|
1515 |
}
|
|
1516 |
|
|
1517 |
pathAliases.put(alias, source);
|
|
1518 |
}
|
|
1519 |
}
|
|
1520 |
finally
|
|
1521 |
{
|
|
1522 |
ds.unbind();
|
|
1523 |
}
|
|
1524 |
}
|
|
1525 |
|
|
1526 |
/**
|
1220 |
1527 |
* This method loads all mapping data from a URL which is calculated
|
1221 |
1528 |
* using the <code>pkgroot</code> and the <code>name_map.xml</code> with
|
1222 |
1529 |
* all "." characters converted to file separators and an extra file
|
... | ... | |
1243 |
1550 |
try
|
1244 |
1551 |
{
|
1245 |
1552 |
p2jMap = new HashMap<>(1024);
|
|
1553 |
p2jNonExtMap = new HashMap<>(1024);
|
1246 |
1554 |
j2pMap = new HashMap<>(1024);
|
1247 |
1555 |
|
1248 |
1556 |
// lookup the descriptors for the original file system
|
... | ... | |
1344 |
1652 |
ExternalProgram ep = buildExternalProgram(pname, jname, classMap);
|
1345 |
1653 |
p2jMap.put(ep.pname, ep);
|
1346 |
1654 |
j2pMap.put(ep.jname, ep);
|
|
1655 |
|
|
1656 |
String nonExtName = pname;
|
|
1657 |
int lastDotIdx = nonExtName.lastIndexOf('.');
|
|
1658 |
if (lastDotIdx > 0)
|
|
1659 |
{
|
|
1660 |
nonExtName = nonExtName.substring(0, lastDotIdx);
|
|
1661 |
}
|
|
1662 |
|
|
1663 |
List<ExternalProgram> progs = p2jNonExtMap.get(nonExtName);
|
|
1664 |
if (progs == null)
|
|
1665 |
{
|
|
1666 |
p2jNonExtMap.put(nonExtName, progs = new ArrayList<>(1));
|
|
1667 |
}
|
|
1668 |
|
|
1669 |
progs.add(ep);
|
1347 |
1670 |
}
|
|
1671 |
|
|
1672 |
loadPathAliases();
|
1348 |
1673 |
}
|
1349 |
1674 |
catch (Exception e)
|
1350 |
1675 |
{
|