I am trying to minify all my css files and one line is making problem. It is background url of .ui-progressbar .ui-progressbar-overlay:
background:url("data:image/gif;base64,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");
I am using granule plugin for compressing with this implementation (first layer):
public class AcceleratorCompressTag extends CompressTag{
public static final String COMPREST_TAG_CONTENT = CompressTag.class.getName() + "Content";
public static final String COMPREST_TAG_JS = CompressTag.class.getName() + "js";
public static final String COMPREST_TAG_CSS = CompressTag.class.getName() + "css";
private static final String NOT_PROCESS_PARAMETER = "granule";
private static final String METHOD = null;
private static final String ID = null;
private static final String OPTIONS = null;
private static final String BASEPATH = null;
private String urlpattern = null;
#Override
public int doAfterBody() throws JspTagException {
final HttpServletRequest httpRequest = new RemoveEncodingHttpServletRequestWrapper(
(HttpServletRequest) pageContext.getRequest(), urlpattern);
final BodyContent bodyContent = getBodyContent();
final String oldBody = bodyContent.getString();
bodyContent.clearBody();
if (httpRequest.getParameter(NOT_PROCESS_PARAMETER) != null) {
final boolean process = CompressorSettings.getBoolean(httpRequest.getParameter(NOT_PROCESS_PARAMETER), false);
if (!process) {
httpRequest.getSession().setAttribute(NOT_PROCESS_PARAMETER, Boolean.TRUE);
} else {
httpRequest.getSession().removeAttribute(NOT_PROCESS_PARAMETER);
}
}
if (httpRequest.getSession().getAttribute(NOT_PROCESS_PARAMETER) != null) {
try {
getPreviousOut().print(oldBody);
} catch (final IOException e) {
throw new JspTagException(e);
}
return SKIP_BODY;
}
try {
final CompressTagHandler compressor = new CompressTagHandler(ID, METHOD, OPTIONS, BASEPATH);
final RealRequestProxy runtimeRequest = new RealRequestProxy(httpRequest);
final String newBody = compressor.handleTag(runtimeRequest, runtimeRequest, oldBody);
getPreviousOut().print(newBody);
} catch (final Exception e) {
throw new JspTagException(e);
}
return SKIP_BODY;
}
public String getUrlpattern() {
return urlpattern;
}
public void setUrlpattern(final String urlpattern) {
this.urlpattern = urlpattern;
}
}
My guess is that i need to perform Base64 Encoding in Java or maybe to increase stack size for JVM because i am getting this error:
SEVERE: Servlet.service() for servlet [CompressServlet] in context with path [] threw exception [Servlet execution threw an exception] with root cause
java.lang.StackOverflowError
at java.util.regex.Pattern$GroupHead.match(Unknown Source)
at java.util.regex.Pattern$Loop.match(Unknown Source)
Can anyone help me with this?
Try using CSS Minifier - By chilts - #andychilton.
https://cssminifier.com/
Related
I am developing an application that reads the XML file and creates the Hash ID based on the details present in XML. As of now, everything is working perfectly, and able to get the List<String>.
I would like to convert this application into Reactive Streams using the Smallrye Mutiny so I went through some of the documentation but did not understand clearly how to convert this application into Reactive Streams where I do not have to wait for the completion of all XML file to return the List<String>. Rather I can start returning the Multi<String> as and when the its generated.
Following is the simple XML that I am reading using SAX Parser to create the Hash ID:
<customerList>
<customer>
<name>Batman</name>
<age>25</age>
</customer>
<customer>
<name>Superman</name>
<age>28</age>
</customer>
</customerList>
Following is the Main application which will make a call to SaxHandler:
public Multi<String> xmlEventHashGenerator(final InputStream xmlStream) throws SAXException, ParserConfigurationException, IOException {
final SAXParserFactory factory = SAXParserFactory.newInstance();
final SaxHandler saxHandler = new SaxHandler();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.newSAXParser().parse(xmlStream, saxHandler);
return Multi.createFrom().emitter(em ->{
saxHandler.getRootNodes().forEach(contextNode -> {
final String preHashString = contextNode.toString();
try {
final StringBuilder hashId = new StringBuilder();
MessageDigest.getInstance("SHA-256").digest(preHashString.getBytes(StandardCharsets.UTF_8));
hashId.append(DatatypeConverter.printHexBinary(digest).toLowerCase());
em.emit(hashId.toString());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
});
em.complete();
});
}
Following is the SaxHandler which will read the XML and create HashIDs:
public class SaxHandler extends DefaultHandler {
#Getter
private final List<String> eventHashIds = new ArrayList<>();
#Getter
private final List<ContextNode> rootNodes = new ArrayList<>();
private final HashMap<String, String> contextHeader = new HashMap<>();
private final String hashAlgorithm;
private ContextNode currentNode = null;
private ContextNode rootNode = null;
private final StringBuilder currentValue = new StringBuilder();
public SaxHandler(final String hashAlgorithm) {
this.hashAlgorithm = hashAlgorithm;
}
#Override
public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) {
if (rootNode == null && qName.equals("customer")) {
rootNode = new ContextNode(contextHeader);
currentNode = rootNode;
rootNode.children.add(new ContextNode(rootNode, "type", qName));
}else if (currentNode != null) {
ContextNode n = new ContextNode(currentNode, qName, (String) null);
currentNode.children.add(n);
currentNode = n;
}
}
#Override
public void characters(char[] ch, int start, int length) {
currentValue.append(ch, start, length);
}
#Override
public void endElement(final String uri, final String localName, final String qName) {
if (rootNode != null && !qName.equals("customer")) {
final String value = !currentValue.toString().trim().equals("") ? currentValue.toString().trim() : null;
currentNode.children.add(new ContextNode(currentNode, qName, value));
}
if (qName.equals("customer")) {
rootNodes.add(rootNode);
rootNode = null;
}
currentValue.setLength(0);
}
}
Following is the Test:
#Test
public void xmlTest() throws Exception {
final HashGenerator eventHashGenerator = new HashGenerator();
final InputStream xmlStream = getClass().getResourceAsStream("/customer.xml");
final List<String> eventHashIds = eventHashGenerator.xmlHashGenerator(xmlStream, "sha3-256");
System.out.println("\nGenerated Event Hash Ids : \n" + eventHashIds);
}
Can someone please guide me to some example or provide some idea on how to convert this application into SmallRye Mutinty Multi<String> based application?
I think you can refactor xmlEventHashGenerator to
public Multi<String> xmlEventHashGenerator(final InputStream xmlStream) throws SAXException, ParserConfigurationException, IOException {
final SAXParserFactory factory = SAXParserFactory.newInstance();
final SaxHandler saxHandler = new SaxHandler();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.newSAXParser().parse(xmlStream, saxHandler);
return Multi.createFrom()
.iterable( saxHandler.getRootNodes() )
.map( RootNode::toString )
.map( this::convertDatatype );
}
private String convertDatatype(String preHashString) {
try {
// I think we could create the MessageDigest instance only once
byte[] digest = MessageDigest.getInstance( "SHA-256" )
.digest( preHashString.getBytes( StandardCharsets.UTF_8 ) );
return DatatypeConverter.printHexBinary( digest ).toLowerCase();
}
catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException( e );
}
}
The test method will look something like:
#Test
public void xmlTest() throws Exception {
final HashGenerator eventHashGenerator = new HashGenerator();
final InputStream xmlStream = getClass().getResourceAsStream("/customer.xml");
System.out.println("Generated Event Hash Ids: ");
eventHashGenerator
.xmlHashGenerator(xmlStream)
// Print all the hash codes
.invoke( hash -> System.out.println( hash )
.await().indefinitely();
}
But if you want to concatenate all the hash codes, you can do:
#Test
public void xmlTest() throws Exception {
final HashGenerator eventHashGenerator = new HashGenerator();
final InputStream xmlStream = getClass()
.getResourceAsStream("/customer.xml");
String hash = eventHashGenerator
.xmlHashGenerator(xmlStream)
// Concatenate all the results
.collect().with( Collectors.joining() );
// Print the hashcode
.invoke( hashcode -> System.out.println("\nGenerated Event Hash Ids : \n" + hashcode) )
.await().indefinitely();
}
To upload files to the repository, I use the following Java-backed WebScript:
public class CustomFileUploader extends DeclarativeWebScript {
private static final String FIRM_DOC = "{http://www.firm.com/model/content/1.0}doc";
private static final String FIRM_DOC_FOLDER = "workspace://SpacesStore/8caf07c3-6aa9-4a41-bd63-404cb3e3ef0f";
private FileFolderService fileFolderService;
private ContentService contentService;
private NodeService nodeService;
private SearchService searchService;
protected Map<String, Object> executeImpl(WebScriptRequest req, Status status) {
processUpload(req);
return null;
}
private void writeContent(NodeRef node, FirmFile firmFile) {
try {
ContentWriter contentWriter = contentService.getWriter(node, ContentModel.PROP_CONTENT, true);
contentWriter.setMimetype(firmFile.getFileMimetype());
contentWriter.putContent(firmFile.getFileContent().getInputStream());
} catch (Exception e) {
e.printStackTrace();
}
}
private NodeRef checkIfNodeExists(String fileName) {
StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
ResultSet resultSet = searchService.query(storeRef, SearchService.LANGUAGE_LUCENE/*LANGUAGE_FTS_ALFRESCO*/,
"TYPE:\"firm:doc\" AND #cm\\:name:" + fileName.replaceAll(" ", "\\ ")+ "");
int len = resultSet.length();
if(len == 0) {
return null;
}
NodeRef node = resultSet.getNodeRef(0);
return node;
}
private NodeRef createNewNode(FirmFile firmFile) {
NodeRef parent = new NodeRef(FIRM_DOC_FOLDER);
NodeRef node = createFileNode(parent, firmFile.getFileName());
return node;
}
private void processUpload(WebScriptRequest req) {
FormData formData = (FormData) req.parseContent();
FormData.FormField[] fields = formData.getFields();
for(FormData.FormField field : fields) {
String fieldName = field.getName();
if(fieldName.equalsIgnoreCase("firm_file") && field.getIsFile()) {
String fileName = field.getFilename();
Content fileContent = field.getContent();
String fileMimetype = field.getMimetype();
NodeRef node = checkIfNodeExists(fileName);
// POJO
FirmFile firm = new FirmFile(fileName, fileContent, fileMimetype, FIRM_DOC);
if(node == null) {
node = createNewNode(firmFile);
}
writeContent(node, firmFile);
}
}
}
private NodeRef createFileNode(NodeRef parentNode, String fileName) {
try {
QName contentQName = QName.createQName(FIRM_DOC);
FileInfo fileInfo = fileFolderService.create(parentNode, fileName, contentQName);
return fileInfo.getNodeRef();
} catch (Exception e) {
e.printStackTrace();
}
}
public FileFolderService getFileFolderService() {
return fileFolderService;
}
public void setFileFolderService(FileFolderService fileFolderService) {
this.fileFolderService = fileFolderService;
}
public ContentService getContentService() {
return contentService;
}
public void setContentService(ContentService contentService) {
this.contentService = contentService;
}
public NodeService getNodeService() {
return nodeService;
}
public void setNodeService(NodeService nodeService) {
this.nodeService = nodeService;
}
public SearchService getSearchService() {
return searchService;
}
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
}
This WebScript works.
There is one problem: if the uploaded file does not exist in the repository, then two versions are created at once: 1.0 and 1.1. Version 1.0 has a size of 0 bytes and has no content.
I think version 1.0 is created here:
FileInfo fileInfo = fileFolderService.create(parentNode, fileName, contentQName);
Version 1.1, perhaps, is created here, when writing the content:
ContentWriter contentWriter = contentService.getWriter(node, ContentModel.PROP_CONTENT, true);
contentWriter.setMimetype(firmFile.getFileMimetype());
contentWriter.putContent(firmFile.getFileContent().getInputStream());
How to prevent the creation of "empty" version 1.0?
Axel Faust gave an excellent answer to this question here.
The problem was in the fileuploader.post.desc.xml descriptor, I didn't set transaction level required to run the web script:
<transaction>none</transaction>
Thus, methods writeContent() and createFileNode() were transactional by default and each of them created its own version of the content.
I set the following:
<transaction>requiresnew</transaction>
The problem is solved. Thank you, #Axel Faust!
I have an xml file with some places in it and their coordinates. I want to show those places on my android app on Google Maps as markers. I have already load the maps.
How could I do this? Any help would be so much appreciated, even if someone could explain it theoritically, as it seems I cant grasp its concept. Can someone help?
example of xml file(placesp.xml):
<placesp>
<placep>
<place_id>1</place_id>
<name>Place1</name>
<description>Place description 1</description>
<coordinates>;40.430224;21.559570</coordinates>
</placep>
<placep>
<place_id>2</place_id>
<name>Place2</name>
<description>Place description 2</description>
<coordinates>;40.423324;21.062439</coordinates>
</placep>
<placep>
<place_id>3</place_id>
<name>Place3</name>
<description>Place description 3</description>
<coordinates>;40.266952;21.238220</coordinates>
</placep>
</placesp>
Maybe you could use a HashMap to save the data.
You just create a new class like this:
public class Coordinates {
public static final HashMap<String, LatLng> COORDINATES = new HashMap<String, LatLng>();
static {
// Place1
COORDINATES.put("Place1", new LatLng(40.430224;21.559570));
}
}
You can access the data stored by the hashmap like this:
locationLatLng = new LatLng(Coordinates.COORDINATES.get("Place1").latitude,Coordinates.COORDINATES.get("Place1").longitude);
And then using this line in the class where you loaded the map to add the markers:
map.addMarker(new MarkerOptions().position(locationLatLng));
I am not really sure how to access data from the xml file, but in theory the logic is the same. You have to get a LatLng coordinate to tell the addMarker method where to put the marker, and thats actually it. I hope I could help you with this.
First you need to create a model class to hold the information for each place. I provide you a sample bellow: Place.class
public class Place {
private int placeId;
private String placeName;
private String placeDescription;
private double placeLongitude;
private double placeLatitude;
public Place() {
super();
}
public int getPlaceId() {
return placeId;
}
public void setPlaceId(final int placeId) {
this.placeId = placeId;
}
public String getPlaceName() {
return placeName;
}
public void setPlaceName(final String placeName) {
this.placeName = placeName;
}
public String getPlaceDescription() {
return placeDescription;
}
public void setPlaceDescription(final String placeDescription) {
this.placeDescription = placeDescription;
}
public double getPlaceLongitude() {
return placeLongitude;
}
public void setPlaceLongitude(final double placeLongitude) {
this.placeLongitude = placeLongitude;
}
public double getPlaceLatitude() {
return placeLatitude;
}
public void setPlaceLatitude(final double placeLatitude) {
this.placeLatitude = placeLatitude;
}
}
Next you will need a XML parser class to retrieve XML data to Place type list. You can use the following sample: PlaceXmlParser.class
public class PlaceXmlParser {
private static final String TAG = PlaceXmlParser.class.getSimpleName();
private static final String PLACE_ID = "place_id";
private static final String PLACE_NAME = "name";
private static final String PLACE_DESCRIPTION = "description";
private static final String PLACE_COORDINATES = "coordinates";
public PlaceXmlParser() {
super();
}
public List<Place> parsePlacesXml(final InputStream xmlStream) {
Place place = null;
final List<Place> placeList = new ArrayList<>();
try {
final XmlPullParserFactory xmlFactoryObject = XmlPullParserFactory.newInstance();
final XmlPullParser parser = xmlFactoryObject.newPullParser();
parser.setInput(xmlStream, null);
int event = parser.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String name = parser.getName();
switch (name) {
case PLACE_ID:
place = new Place();
setPlaceId(parser, place);
break;
case PLACE_NAME:
setPlaceName(parser, place);
break;
case PLACE_DESCRIPTION:
setPlaceDescription(parser, place);
break;
case PLACE_COORDINATES:
setPlaceLatLong(parser, place);
placeList.add(place);
break;
}
}
event = parser.next();
}
} catch (final XmlPullParserException e) {
Log.e(TAG, e.toString());
} catch (final IOException e) {
Log.e(TAG, e.toString());
}
return placeList;
}
private boolean areValidArgs(final XmlPullParser parser, final Place place) {
return null != parser && null != place;
}
private void setPlaceId(final XmlPullParser parser, final Place place) {
if (areValidArgs(parser, place)) {
final String placeId = getTagValue(parser);
place.setPlaceId(Integer.parseInt(placeId));
}
}
private void setPlaceName(final XmlPullParser parser, final Place place) {
if (areValidArgs(parser, place)) {
final String placeName = getTagValue(parser);
place.setPlaceName(placeName);
}
}
private void setPlaceDescription(final XmlPullParser parser, final Place place) {
if (areValidArgs(parser, place)) {
final String placeDescription = getTagValue(parser);
place.setPlaceDescription(placeDescription);
}
}
private void setPlaceLatLong(final XmlPullParser parser, final Place place) {
if (areValidArgs(parser, place)) {
final String[] latLong = getTagValue(parser).split(";");
if (3 == latLong.length) {
place.setPlaceLatitude(Double.parseDouble(latLong[1]));
place.setPlaceLongitude(Double.parseDouble(latLong[2]));
}
}
}
private String getTagValue(final XmlPullParser parser) {
String result = "";
try {
if (parser.next() == XmlPullParser.TEXT) {
result = parser.getText();
parser.nextTag();
}
} catch (final XmlPullParserException e) {
Log.e(TAG, e.toString());
} catch (final IOException e) {
Log.e(TAG, e.toString());
}
return result;
}
}
Finally, in you Google Map's activity, implement OnMapReadyCallback interface, override onMapReady method and add place markers to Google Map: MapActivity.class
public class MapActivity extends FragmentActivity implements OnMapReadyCallback {
private GoogleMap mMap;
private List<Place> placeList;
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map);
this.placeList = getPlaceList();
}
#Override
public void onMapReady(final GoogleMap googleMap) {
this.mMap = googleMap;
addPlaceListMarkersToGoogleMap();
}
private void addPlaceListMarkersToGoogleMap() {
for (final Place place : this.placeList) {
final LatLong latLong = new LatLong(place.getPlaceLatitude(), place.getPlaceLongitude());
this.mMap.addMarker(new MarkerOptions().position(latLong).title(place.getPlaceName()));
}
}
private List<Place> getPlaceList() {
final String xmlString = "<placesp>" +
"<placep>" +
" <place_id>1</place_id>" +
" <name>Place1</name>" +
" <description>Place description 1</description>" +
" <coordinates>;40.430224;21.559570</coordinates>" +
"</placep>" +
"<placep>" +
" <place_id>2</place_id>" +
" <name>Place2</name>" +
" <description>Place description 2</description>" +
" <coordinates>;40.423324;21.062439</coordinates>" +
"</placep>" +
"<placep>" +
" <place_id>3</place_id>" +
" <name>Place3</name>" +
" <description>Place description 3</description>" +
" <coordinates>;40.266952;21.238220</coordinates>" +
"</placep>" +
"</placesp>";
final InputStream xmlStream = getXmlStream(xmlString);
final PlaceXmlParser parser = new PlaceXmlParser();
return parser.parsePlacesXml(xmlStream);
}
private InputStream getXmlStream(final String xmlString) {
InputStream xmlStream = null;
try {
xmlStream = new ByteArrayInputStream(xmlString.getBytes("UTF-8"));
} catch (final UnsupportedEncodingException e) {
e.printStackTrace();
}
return xmlStream;
}
}
Provided code works well for given XML sample, be aware of possible exceptions and handle it. Hope this help!
Has anyone an idea about what is wrong with my attempt to call a method from a C# dll in my Java code?
Here is my example:
Java code:
public class CsDllHandler {
public interface IKeywordRun extends Library {
public String KeywordRun(String action, String xpath, String inputData,
String verifyData);
}
private static IKeywordRun jnaInstance = null;
public void runDllMethod(String action, String xpath, String inputData,
String verifyData) {
NativeLibrary.addSearchPath(${projectDllName},
"${projectPath}/bin/x64/Debug");
jnaInstance = (IKeywordRun) Native.loadLibrary(
${projectDllName}, IKeywordRun.class);
String csResult = jnaInstance.KeywordRun(action, xpath, inputData,
verifyData);
System.out.println(csResult);
}
}
And in C#:
[RGiesecke.DllExport.DllExport]
public static string KeywordRun(string action, string xpath, string inputData, string verifyData) {
return "C# here";
}
The Unmanaged Exports nuget should be enough for me to call this method (in theory) but I have some strange error:
Exception in thread "main" java.lang.Error: Invalid memory access
at com.sun.jna.Native.invokePointer(Native Method)
at com.sun.jna.Function.invokePointer(Function.java:470)
at com.sun.jna.Function.invokeString(Function.java:651)
at com.sun.jna.Function.invoke(Function.java:395)
at com.sun.jna.Function.invoke(Function.java:315)
at com.sun.jna.Library$Handler.invoke(Library.java:212)
at com.sun.proxy.$Proxy0.KeywordRun(Unknown Source)
at auto.test.keywords.utils.CsDllHandler.runDllMethod(CsDllHandler.java:34)
at auto.test.keywords.runner.MainClass.main(MainClass.java:24)
Well, after another day of research and "trial and error" I have found the cause of my problem and a solution.
The cause was that my C# dll had a dependency on log4net.dll. For running a static method from a standalone C# dll the code from the question is all you need.
The solution for using C# dll with dependencies is to create another dll with no dependency and to load the original dll in this adapter with reflection. In Java you should load the adapter dll with jna and call any exported method. I was able not only to execute methods from the adapter but also to configure log4net with reflection and Java
Here is my code:
(C#)
public class CSharpDllHandler {
private static Logger log = Logger.getLogger(CSharpDllHandler.class);
public interface IFrameworkAdapter extends Library {
public String runKeyword(String action, String xpath, String inputData,
String verifyData);
public String configureLog4net(String log4netConfigPath);
public String loadAssemblies(String frameworkDllPath,
String log4netDllPath);
}
private static IFrameworkAdapter jnaAdapterInstance = null;
private String jnaSearchPath = null;
public CSharpDllHandler(String searchPath) {
this.jnaSearchPath = searchPath;
// add to JNA search path
System.setProperty("jna.library.path", jnaSearchPath);
// load attempt
jnaAdapterInstance = (IFrameworkAdapter) Native.loadLibrary(
"FrameworkAdapter", IFrameworkAdapter.class);
}
public String loadAssemblies(String frameworkDllPath, String log4netDllPath) {
String csResult = jnaAdapterInstance.loadAssemblies(frameworkDllPath,
log4netDllPath);
log.debug(csResult);
return csResult;
}
public String runKeyword(String action, String xpath, String inputData,
String verifyData) {
String csResult = jnaAdapterInstance.runKeyword(action, xpath,
inputData, verifyData);
log.debug(csResult);
return csResult;
}
public String configureLogging(String log4netConfigPath) {
String csResult = jnaAdapterInstance
.configureLog4net(log4netConfigPath);
log.debug(csResult);
return csResult;
}
public String getJnaSearchPath() {
return jnaSearchPath;
}
}
In the main method just use something like this:
CSharpDllHandler dllHandler = new CSharpDllHandler(
${yourFrameworkAdapterDllLocation});
dllHandler.loadAssemblies(
${yourOriginalDllPath},${pathToTheUsedLog4netDllFile});
dllHandler.configureLogging(${log4net.config file path});
dllHandler.runKeyword("JAVA Action", "JAVA Xpath", "JAVA INPUT",
"JAVA VERIFY");
dllHandler.runKeyword("JAVA Action2", "JAVA Xpath2", "JAVA INPUT2",
"JAVA VERIFY2");
In C# I have the desired methods on the original dll:
public static string KeywordRun(string action, string xpath, string inputData, string verifyData) {
log.Debug("Action = " + action);
log.Debug("Xpath = " + xpath);
log.Debug("InputData = " + inputData);
log.Debug("VerifyData = " + verifyData);
return "C# UserActions result: "+ action+" "+xpath+" "+inputData+" "+verifyData;
}
and all the magic is in the DLL Adapter:
namespace FrameworkAdapter {
[ComVisible(true)]
public class FwAdapter {
private const String OK="OK";
private const String frameworkEntryClassName = "${nameOfTheDllClass with method to run }";
private const String log4netConfiguratorClassName = "log4net.Config.XmlConfigurator";
private static Assembly frameworkDll = null;
private static Type frameworkEntryClass = null;
private static MethodInfo keywordRunMethod = null;
private static Assembly logDll = null;
private static Type logEntryClass = null;
private static MethodInfo logConfigureMethod = null;
private static String errorMessage = "OK";
[RGiesecke.DllExport.DllExport]
public static string loadAssemblies(string frameworkDllPath, string log4netDllPath) {
try {
errorMessage = LoadFrameworkDll(frameworkDllPath, frameworkEntryClassName);
LoadFrameworkMethods("KeywordRun", "Setup", "TearDown");
errorMessage = LoadLogAssembly(log4netDllPath, log4netConfiguratorClassName);
if (errorMessage.CompareTo(OK) == 0)
errorMessage = LoadLogMethods("Configure");
}
catch (Exception e) {
return e.Message;
}
return errorMessage;
}
[RGiesecke.DllExport.DllExport]
public static string configureLog4net(string log4netConfigPath) {
if (errorMessage.CompareTo("OK") == 0) {
StringBuilder sb = new StringBuilder();
sb.AppendLine("Try to configure Log4Net");
try {
FileInfo logConfig = new FileInfo(log4netConfigPath);
logConfigureMethod.Invoke(null, new object[] { logConfig });
sb.AppendLine("Log4Net configured");
}
catch (Exception e) {
sb.AppendLine(e.InnerException.Message);
}
return sb.ToString();
}
return errorMessage;
}
[RGiesecke.DllExport.DllExport]
public static string runKeyword(string action, string xpath, string inputData, string verifyData) {
StringBuilder sb = new StringBuilder();
object result = null;
try {
result = keywordRunMethod.Invoke(null, new object[] { action, xpath, inputData, verifyData });
sb.AppendLine(result.ToString());
}
catch (Exception e) {
sb.AppendLine(e.InnerException.Message);
}
return sb.ToString();
}
private static String LoadFrameworkDll(String dllFolderPath, String entryClassName) {
try {
frameworkDll = Assembly.LoadFrom(dllFolderPath);
Type[] dllTypes = frameworkDll.GetExportedTypes();
foreach (Type t in dllTypes)
if (t.FullName.Equals(entryClassName)) {
frameworkEntryClass = t;
break;
}
}
catch (Exception e) {
return e.InnerException.Message;
}
return OK;
}
private static String LoadLogAssembly(String dllFolderPath, String entryClassName) {
try {
logDll = Assembly.LoadFrom(dllFolderPath);
Type[] dllTypes = logDll.GetExportedTypes();
foreach (Type t in dllTypes)
if (t.FullName.Equals(entryClassName)) {
logEntryClass = t;
break;
}
}
catch (Exception e) {
return e.InnerException.Message;
}
return OK;
}
private static String LoadLogMethods(String logMethodName) {
try {
logConfigureMethod = logEntryClass.GetMethod(logMethodName, new Type[] { typeof(FileInfo) });
}
catch (Exception e) {
return e.Message;
}
return OK;
}
private static void LoadFrameworkMethods(String keywordRunName, String scenarioSetupName, String scenarioTearDownName) {
///TODO load the rest of the desired methods here
keywordRunMethod = frameworkEntryClass.GetMethod(keywordRunName);
}
}
}
Running this code will provide all the logged messages from the original C# DLL to the Java console output (and to a file if configured). In a similar way, we can load any other needed dll files for runtime.
Please forgive my [very probable wrong] way of doing things in C# with reflection, I'm new to this language.
I'm running a small Android project which could read RSS/Atom Feed documents, using SAX library. Everything works well for default RSS sources, but with minimized sources (without spaces or new line tokens), it produces nothing but a list of blank items. My logs in Log cat also display nothing. I double check this problems with variant RSS sites, but problems still there. Below is my inheritance class of DefaultHandler which I use to handle Rss sources
public class RssContentHandler extends DefaultHandler {
private static final int UNKNOWN_STATE = -1;
private static final int ELEMENT_START = 0;
private static final int TITLE_END = 1;
private static final int DESCRIPTION_END = 2;
private static final int LINK_END = 3;
private static final int PUBDATE_END = 4;
private static final int CHANNEL_END = 5;
private int iState = UNKNOWN_STATE;
private String fullCharacters;
private boolean itemFound = false;
private RssItem rssItem;
private RssFeed rssFeed;
public RssContentHandler() {
}
public RssFeed getFeed() {
return this.rssFeed;
}
#Override
public void startDocument() {
rssItem = new RssItem();
rssFeed = new RssFeed();
Log.i("startDocument", "startDocument");
}
#Override
public void endDocument() {
}
#Override
public void startElement(String _uri, String _localName, String _qName, Attributes _attributes) {
if (_localName.equalsIgnoreCase("item")) {
itemFound = true;
rssItem = new RssItem();
this.iState = UNKNOWN_STATE;
} else
this.iState = ELEMENT_START;
fullCharacters = "";
}
#Override
public void endElement(String _uri, String _localName, String _qName) {
if (_localName.equalsIgnoreCase("item"))
this.rssFeed.addItem(this.rssItem);
else if (_localName.equalsIgnoreCase("title"))
this.iState = TITLE_END;
else if (_localName.equalsIgnoreCase("description"))
this.iState = DESCRIPTION_END;
else if (_localName.equalsIgnoreCase("link"))
this.iState = LINK_END;
else if (_localName.equalsIgnoreCase("pubDate"))
this.iState = PUBDATE_END;
else if (_localName.equalsIgnoreCase("channel"))
this.iState = CHANNEL_END;
else
this.iState = UNKNOWN_STATE;
}
#Override
public void characters(char[] _ch, int _start, int _length) {
String strCharacters = new String(_ch, _start, _length);
if (this.iState == ELEMENT_START)
fullCharacters += strCharacters;
else {
if (!itemFound) {
switch (this.iState) {
case TITLE_END:
this.rssFeed.setTitle(fullCharacters);
break;
case DESCRIPTION_END:
this.rssFeed.setDescription(fullCharacters);
break;
case LINK_END:
this.rssFeed.setLink(fullCharacters);
break;
case PUBDATE_END:
this.rssFeed.setPubDate(fullCharacters);
break;
}
} else {
switch (this.iState) {
case TITLE_END:
this.rssItem.setTitle(fullCharacters);
Log.i("characters", fullCharacters);
break;
case DESCRIPTION_END:
this.rssItem.setDescription(fullCharacters);
break;
case LINK_END:
this.rssItem.setLink(fullCharacters);
break;
case PUBDATE_END:
this.rssItem.setPubDate(fullCharacters);
break;
}
}
this.iState = UNKNOWN_STATE;
}
}
}
and snippet to setup the parser:
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet();
try {
request.setURI(new URI(_strUrl));
} catch (URISyntaxException e) {
e.printStackTrace();
}
HttpResponse response = client.execute(request);
Reader inputStream = new InputStreamReader(response.getEntity().getContent());
RssContentHandler rssContentHandler = new RssContentHandler();
InputSource inputSource = new InputSource();
inputSource.setCharacterStream(inputStream);
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
SAXParser saxParser = saxParserFactory.newSAXParser();
saxParser.parse(inputSource, rssContentHandler);
this.rssFeed = rssContentHandler.getFeed();
P/s: i'm using Android 2.3 x86 installed on VirtualBox for Debugging, and these sources work fine with the built-in RSS Reader app come with the x86 version. So what's wrong here?
Try with _qName instead of _localName.
Your xml contains CDATA so You cann't parse the XML response with your current parser. You have to use LexicalHandler for parsing Raw HTML.
public class MyHandler implements LexicalHandler {
public void startDTD(String name, String publicId, String systemId)
throws SAXException {}
public void endDTD() throws SAXException {}
public void startEntity(String name) throws SAXException {}
public void endEntity(String name) throws SAXException {}
public void startCDATA() throws SAXException {}
public void endCDATA() throws SAXException {}
public void comment (char[] text, int start, int length)
throws SAXException {
String comment = new String(text, start, length);
System.out.println(comment);
}
You can also parse your XML with DOM if memory is not the issue. For more help visit Handling Lexical Events