From: Niels Erik G. Nielsen Date: Fri, 8 Mar 2013 17:44:32 +0000 (-0500) Subject: Work on error reporting. Adds troubleshooter. X-Git-Tag: v0.0.7~217 X-Git-Url: http://sru.miketaylor.org.uk/cgi-bin?a=commitdiff_plain;h=45eb77e915522940923b9401b984bff004935686;p=mkjsf-moved-to-github.git Work on error reporting. Adds troubleshooter. --- diff --git a/src/META-INF/resources/pz2utils/pz2watch.xhtml b/src/META-INF/resources/pz2utils/pz2watch.xhtml index bfa2b3b..dfa628d 100644 --- a/src/META-INF/resources/pz2utils/pz2watch.xhtml +++ b/src/META-INF/resources/pz2utils/pz2watch.xhtml @@ -35,8 +35,13 @@ - -

+ +

+ + + #{suggestion} + +
diff --git a/src/main/java/com/indexdata/pz2utils4jsf/config/Pz2Configurator.java b/src/main/java/com/indexdata/pz2utils4jsf/config/Pz2Configurator.java index 88b275d..b4e8eab 100644 --- a/src/main/java/com/indexdata/pz2utils4jsf/config/Pz2Configurator.java +++ b/src/main/java/com/indexdata/pz2utils4jsf/config/Pz2Configurator.java @@ -2,7 +2,10 @@ package com.indexdata.pz2utils4jsf.config; import java.io.IOException; import java.io.Serializable; +import java.util.List; public interface Pz2Configurator extends Serializable { public Pz2Config getConfig() throws IOException; + + public List document(); } diff --git a/src/main/java/com/indexdata/pz2utils4jsf/config/Pz2ConfigureByMk2Config.java b/src/main/java/com/indexdata/pz2utils4jsf/config/Pz2ConfigureByMk2Config.java index 2273b7a..f10aa1e 100644 --- a/src/main/java/com/indexdata/pz2utils4jsf/config/Pz2ConfigureByMk2Config.java +++ b/src/main/java/com/indexdata/pz2utils4jsf/config/Pz2ConfigureByMk2Config.java @@ -1,6 +1,8 @@ package com.indexdata.pz2utils4jsf.config; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import javax.enterprise.context.SessionScoped; import javax.enterprise.inject.Alternative; @@ -16,6 +18,8 @@ import com.indexdata.masterkey.config.MasterkeyConfiguration; import com.indexdata.masterkey.config.ModuleConfiguration; import com.indexdata.pz2utils4jsf.utils.Utils; +import static com.indexdata.pz2utils4jsf.utils.Utils.nl; + @Named @SessionScoped @Alternative public class Pz2ConfigureByMk2Config implements Pz2Configurator { @@ -43,21 +47,28 @@ public class Pz2ConfigureByMk2Config implements Pz2Configurator { "pazpar-application-jsf", ((HttpServletRequest) externalContext.getRequest()).getServerName()); ModuleConfiguration moduleConfig = mkConfigContext.getModuleConfiguration("pz2client"); pz2config = new Pz2Config(moduleConfig); - logger.info("Accessing Pazpar2 at: " +pz2config.get("PAZPAR2_URL")); + logger.info(document()); + } + + + public List document() { + List doc = new ArrayList(); + + doc.add("Set to access Pazpar2 at: " +pz2config.get("PAZPAR2_URL")); if (pz2config.get("PAZPAR2_SERVICE_XML") != null) { - logger.info("Using the service definition contained in " + pz2config.getConfigFilePath() + "/" + pz2config.get("PAZPAR2_SERVICE_XML")); + doc.add("Set to use the service definition contained in " + pz2config.getConfigFilePath() + "/" + pz2config.get("PAZPAR2_SERVICE_XML")); if (pz2config.get("PAZPAR2_SETTINGS_XML") != null) { - logger.info("Using the target settings contained in " + pz2config.getConfigFilePath() + "/" + pz2config.get("PAZPAR2_SETTINGS_XML")); + doc.add("Set to use the target settings contained in " + pz2config.getConfigFilePath() + "/" + pz2config.get("PAZPAR2_SETTINGS_XML")); } else { - logger.info("Using the server side target settings as defined in the service definition."); + doc.add("Set to use the server side target settings as defined in the service definition."); } } else if (pz2config.get("PAZPAR2_SERVICE_ID") != null) { - logger.info("Using the server side service definition identified by service id "+pz2config.get("PAZPAR2_SERVICE_ID")); + doc.add("Set to use the server side service definition identified by service id "+pz2config.get("PAZPAR2_SERVICE_ID")); } else { - logger.error("Did not find service ID nor service definition XML file so set up pazpar2 service."); + doc.add("Error: Did not find service ID nor service definition XML file to set up pazpar2 service."); } - + return doc; } - + } diff --git a/src/main/java/com/indexdata/pz2utils4jsf/config/Pz2ConfigureByWebXml.java b/src/main/java/com/indexdata/pz2utils4jsf/config/Pz2ConfigureByWebXml.java index d131e94..ff38ae7 100644 --- a/src/main/java/com/indexdata/pz2utils4jsf/config/Pz2ConfigureByWebXml.java +++ b/src/main/java/com/indexdata/pz2utils4jsf/config/Pz2ConfigureByWebXml.java @@ -1,7 +1,9 @@ package com.indexdata.pz2utils4jsf.config; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.enterprise.context.SessionScoped; @@ -40,4 +42,10 @@ public class Pz2ConfigureByWebXml implements Pz2Configurator { parameters.put("PAZPAR2_SERVICE_ID", servletContext.getInitParameter("PAZPAR2_SERVICE_ID")); pz2config = new Pz2Config(parameters); } + + public List document() { + List doc = new ArrayList(); + doc.add("No documentation written yet for this configurator"); + return doc; + } } diff --git a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/ApplicationTroubleshooter.java b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/ApplicationTroubleshooter.java new file mode 100644 index 0000000..a30bc54 --- /dev/null +++ b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/ApplicationTroubleshooter.java @@ -0,0 +1,53 @@ +package com.indexdata.pz2utils4jsf.pazpar2; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.log4j.Logger; + +import com.indexdata.pz2utils4jsf.config.Pz2Configurator; +import com.indexdata.pz2utils4jsf.utils.Utils; +import static com.indexdata.pz2utils4jsf.utils.Utils.nl; + +public class ApplicationTroubleshooter { + + private static Pattern httpResponsePattern = Pattern.compile("Unexpected HTTP response code \\(([0-9]*)\\).*"); + private static Logger logger = Logger.getLogger(ApplicationTroubleshooter.class); + + private Pz2Configurator configurator = null; + + public ApplicationTroubleshooter(Pz2Configurator configurator) { + this.configurator = configurator; + } + + public ArrayList getSuggestions(String commandName, String errorMessage) { + ArrayList suggestions = new ArrayList(); + if (errorMessage.startsWith("Unexpected HTTP response")) { + Matcher m = httpResponsePattern.matcher(errorMessage); + if (m.matches()) { + String errorCode = m.group(1); + if (errorCode.equals("404")) { + suggestions.add("Pazpar2 service not found (response code 404). "); + suggestions.add("Please check the PAZPAR2_URL configuration and verify " + + "that a pazpar2 service is running at the given address."); + suggestions.add("The application was configured using " + Utils.baseObjectName(configurator)); + suggestions.add("The configurator reports following configuration was used: "); + suggestions.addAll(configurator.document()); + } else { + suggestions.add("Response code was " + errorCode + ". " + nl + + "Please check the PAZPAR2_URL configuration and verify " + + "that a pazpar2 service is running at the given address." + nl); + } + } else { + logger.warn("Found message but no pattern match"); + } + } + if (errorMessage == null || errorMessage.length()==0) { + logger.debug("No error message found, no suggestions made."); + } else { + logger.info("No suggestions yet for message " + errorMessage); + } + return suggestions; + } +} diff --git a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/CommandThread.java b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/CommandThread.java index 5764777..50ec2b6 100644 --- a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/CommandThread.java +++ b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/CommandThread.java @@ -5,12 +5,10 @@ import java.io.IOException; import org.apache.log4j.Logger; -import com.indexdata.pz2utils4jsf.pazpar2.CommandThread; -import com.indexdata.pz2utils4jsf.pazpar2.Pazpar2Command; -import com.indexdata.utils.XmlUtils; import com.indexdata.masterkey.pazpar2.client.ClientCommand; import com.indexdata.masterkey.pazpar2.client.Pazpar2Client; import com.indexdata.masterkey.pazpar2.client.exceptions.Pazpar2ErrorException; +import com.indexdata.pz2utils4jsf.pazpar2.data.ApplicationError; public class CommandThread extends Thread { @@ -25,6 +23,16 @@ public class CommandThread extends Thread { this.client = client; } + /** + * Runs the specified command using the specified Pazpar2 client + * Sets the Pazpar2 response as an XML response string to be retrieved by + * getResponse(). + * + * In case of an exception, an error response is generated, the document + * element being the same as it would have been if successful (named after + * the command, that is). + * + */ public void run() { ClientCommand clientCommand = new ClientCommand(command.getName(), command.getEncodedQueryString()); if (command.getName().equals("search")) { @@ -37,20 +45,22 @@ public class CommandThread extends Thread { long end = System.currentTimeMillis(); logger.debug("Executed " + command.getName() + " in " + (end-start) + " ms." ); } catch (IOException e) { - logger.error("Message: " + e.getMessage()); - response = new StringBuilder("<"+command.getName()+">"+XmlUtils.escape(e.getMessage())+""); + response.append(ApplicationError.createErrorXml(command.getName(), "io", e.getMessage())); logger.error(response.toString()); } catch (Pazpar2ErrorException e) { - logger.error(e.getMessage()); - response = new StringBuilder("<"+command.getName()+">"+XmlUtils.escape(e.getMessage())+""); + response.append(ApplicationError.createErrorXml(command.getName(), "pazpar2error", e.getMessage())); logger.error(response.toString()); } } + /** + * + * @return Pazpar2 response as an XML string, possibly a generated error XML + */ public String getResponse () { return response.toString(); } - + public Pazpar2Command getCommand() { return command; } diff --git a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/Pz2Bean.java b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/Pz2Bean.java index 6abdb34..abab426 100644 --- a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/Pz2Bean.java +++ b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/Pz2Bean.java @@ -12,6 +12,7 @@ import org.apache.log4j.Logger; import com.indexdata.pz2utils4jsf.config.Pz2Configurator; import com.indexdata.pz2utils4jsf.controls.ResultsPager; +import com.indexdata.pz2utils4jsf.pazpar2.data.ApplicationError; import com.indexdata.pz2utils4jsf.pazpar2.data.ByTarget; import com.indexdata.pz2utils4jsf.pazpar2.data.RecordResponse; import com.indexdata.pz2utils4jsf.pazpar2.data.ShowResponse; @@ -276,13 +277,9 @@ public class Pz2Bean implements Pz2Interface, Serializable { public boolean hasErrors() { return pz2.hasErrors(); } - - public String getErrorMessages() { - return pz2.getErrorMessages(); - } - - public String getFirstErrorMessage() { - return pz2.getFirstErrorMessage(); + + public ApplicationError getOneError() { + return pz2.getOneError(); } diff --git a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/Pz2Interface.java b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/Pz2Interface.java index b69b4e2..d7fb64c 100644 --- a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/Pz2Interface.java +++ b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/Pz2Interface.java @@ -5,6 +5,7 @@ import java.util.List; import com.indexdata.pz2utils4jsf.controls.ResultsPager; import com.indexdata.pz2utils4jsf.pazpar2.TargetFilter; +import com.indexdata.pz2utils4jsf.pazpar2.data.ApplicationError; import com.indexdata.pz2utils4jsf.pazpar2.data.ByTarget; import com.indexdata.pz2utils4jsf.pazpar2.data.RecordResponse; import com.indexdata.pz2utils4jsf.pazpar2.data.ShowResponse; @@ -300,9 +301,7 @@ public interface Pz2Interface extends Serializable { public void setCurrentStateKey(String key); public boolean hasErrors(); - - public String getErrorMessages(); - - public String getFirstErrorMessage(); + + public ApplicationError getOneError(); } diff --git a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/Pz2Session.java b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/Pz2Session.java index 822b416..74980bc 100644 --- a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/Pz2Session.java +++ b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/Pz2Session.java @@ -15,6 +15,7 @@ import org.apache.log4j.Logger; import com.indexdata.masterkey.pazpar2.client.exceptions.ProxyErrorException; import com.indexdata.pz2utils4jsf.config.Pz2Configurator; import com.indexdata.pz2utils4jsf.controls.ResultsPager; +import com.indexdata.pz2utils4jsf.pazpar2.data.ApplicationError; import com.indexdata.pz2utils4jsf.pazpar2.data.ByTarget; import com.indexdata.pz2utils4jsf.pazpar2.data.Pazpar2ResponseData; import com.indexdata.pz2utils4jsf.pazpar2.data.Pazpar2ResponseParser; @@ -40,7 +41,8 @@ public class Pz2Session implements Pz2Interface { private com.indexdata.masterkey.pazpar2.client.Pazpar2Client client = null; private TargetFilter targetFilter = null; private ResultsPager pager = null; - + private ApplicationTroubleshooter errorHelper = null; + public Pz2Session () { logger.info("Instantiating pz2 session object [" + Utils.objectId(this) + "]"); } @@ -48,15 +50,16 @@ public class Pz2Session implements Pz2Interface { public void init(Pz2Configurator pz2conf) { if (client==null) { logger.info(Utils.objectId(this) + " is configuring itself using the provided " + Utils.objectId(pz2conf)); - try { - cfg = new com.indexdata.masterkey.pazpar2.client.Pazpar2ClientConfiguration(pz2conf.getConfig()); - client = new com.indexdata.masterkey.pazpar2.client.Pazpar2ClientGeneric(cfg); - resetDataObjects(); - } catch (ProxyErrorException e) { - e.printStackTrace(); - } catch (IOException ioe) { - ioe.printStackTrace(); - } + try { + cfg = new com.indexdata.masterkey.pazpar2.client.Pazpar2ClientConfiguration(pz2conf.getConfig()); + client = new com.indexdata.masterkey.pazpar2.client.Pazpar2ClientGeneric(cfg); + errorHelper = new ApplicationTroubleshooter(pz2conf); + resetDataObjects(); + } catch (ProxyErrorException e) { + e.printStackTrace(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } } else { logger.warn("Attempt to configure session but it already has a configured client"); } @@ -111,7 +114,9 @@ public class Pz2Session implements Pz2Interface { } } for (CommandThread thread : threadList) { - dataObjects.put(thread.getCommand().getName(), new Pazpar2ResponseParser().getObject(thread.getResponse())); + String commandName = thread.getCommand().getName(); + Pazpar2ResponseData responseObject = Pazpar2ResponseParser.getParser().getDataObject(thread.getResponse()); + dataObjects.put(commandName, responseObject); } return getActiveClients(); } else { @@ -268,41 +273,43 @@ public class Pz2Session implements Pz2Interface { queryStates.setCurrentStateKey(key); } + /** + * Returns true if application error found in any response data objects + */ public boolean hasErrors () { - if (dataObjects.get("search").isError()) { + if (dataObjects.get("search").hasApplicationError()) { logger.info("Error detected in search"); return true; } for (String name : dataObjects.keySet()) { - if (dataObjects.get(name).isError()) { + if (dataObjects.get(name).hasApplicationError()) { logger.info("Error detected in " + name); return true; } } return false; } - - public String getErrorMessages() { - StringBuilder msgs = new StringBuilder(""); - for (String name : dataObjects.keySet()) { - if (dataObjects.get(name).isError()) { - msgs.append(name + ": " + dataObjects.get(name).getErrorMessage()); - } - } - return msgs.toString(); - } + - public String getFirstErrorMessage() { - if (dataObjects.get("search").isError()) { - return "Error doing search: " + dataObjects.get("search").getErrorMessage(); - } - for (String name : dataObjects.keySet()) { - if (dataObjects.get(name).isError()) { - return name + ": " + dataObjects.get(name).getErrorMessage(); - } + /** + * Returns a search command error, if any, otherwise the first + * error found for an arbitrary command, if any, otherwise + * an empty dummy error. + */ + public ApplicationError getOneError() { + ApplicationError error = new ApplicationError(); + if (dataObjects.get("search").hasApplicationError()) { + error = dataObjects.get("search").getApplicationError(); + } else { + for (String name : dataObjects.keySet()) { + if (dataObjects.get(name).hasApplicationError()) { + error = dataObjects.get(name).getApplicationError(); + break; + } + } } - return ""; - + error.setTroubleshooter(errorHelper); + return error; } @@ -332,6 +339,10 @@ public class Pz2Session implements Pz2Interface { return pager; } + protected ApplicationTroubleshooter getTroubleshooter() { + return errorHelper; + } + private void handleQueryStateChanges (String commands) { if (queryStates.hasPendingStateChange("search")) { logger.debug("Found pending search change. Doing search before updating " + commands); diff --git a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/ApplicationError.java b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/ApplicationError.java new file mode 100644 index 0000000..5e95712 --- /dev/null +++ b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/ApplicationError.java @@ -0,0 +1,65 @@ +package com.indexdata.pz2utils4jsf.pazpar2.data; + +import static com.indexdata.pz2utils4jsf.utils.Utils.nl; + +import java.util.ArrayList; +import java.util.List; + +import com.indexdata.pz2utils4jsf.pazpar2.ApplicationTroubleshooter; +import com.indexdata.utils.XmlUtils; + +public class ApplicationError extends Pazpar2ResponseData { + + private static final long serialVersionUID = 8878776025779714122L; + private ApplicationTroubleshooter errorHelper = null; + + + public ApplicationError () { + } + + public String getCommandName() { + return getOneElementValue("commandname"); + } + + public String getErrorMessage() { + return getOneElementValue("errormessage"); + } + + public String getException () { + return getOneElementValue("exception"); + } + + public List getSuggestions() { + if (errorHelper!=null) { + return errorHelper.getSuggestions(getCommandName(), getErrorMessage()); + } else { + List nohelper = new ArrayList(); + nohelper.add("Tips: could not generate tips due to a programming error, error helper was not set"); + return nohelper; + } + } + + /** + * Creates an XML string error message, embedded in an XML string document named by the command + * @param commandName + * @param exceptionName + * @param errorMessage + * @return + */ + public static String createErrorXml (String commandName, String exceptionName, String errorMessage) { + StringBuilder errorXml = new StringBuilder(""); + errorXml.append("<" + commandName + ">"+nl); + errorXml.append(" "+nl); + errorXml.append(" " + commandName + ""); + errorXml.append(" " + XmlUtils.escape(exceptionName) + ""+nl); + errorXml.append(" " + XmlUtils.escape(errorMessage) + ""+nl); + errorXml.append(" "+nl); + errorXml.append(""+nl); + return errorXml.toString(); + } + + public void setTroubleshooter (ApplicationTroubleshooter errorHelper) { + this.errorHelper = errorHelper; + } + +} diff --git a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/Pazpar2ErrorResponse.java b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/Pazpar2ErrorResponse.java deleted file mode 100644 index ff6a530..0000000 --- a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/Pazpar2ErrorResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.indexdata.pz2utils4jsf.pazpar2.data; - -public class Pazpar2ErrorResponse extends Pazpar2ResponseData { - - private static final long serialVersionUID = 5261091417784893149L; - -} diff --git a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/Pazpar2ResponseData.java b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/Pazpar2ResponseData.java index a01ca76..8be2d24 100644 --- a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/Pazpar2ResponseData.java +++ b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/Pazpar2ResponseData.java @@ -14,7 +14,7 @@ public class Pazpar2ResponseData implements Serializable { HashMap attributes = new HashMap(); HashMap> elements = new HashMap>(); String textContent = ""; - String errorText = null; + ApplicationError error = null; public void setType (String type) { this.type = type; @@ -54,6 +54,12 @@ public class Pazpar2ResponseData implements Serializable { } } + /** + * Returns the text content of the first element found with the given + * name + * @param name of the element + * @return text value, empty string if none found + */ public String getOneElementValue (String name) { if (getOneElement(name)!=null && getOneElement(name).getValue().length()>0) { return getOneElement(name).getValue(); @@ -88,13 +94,15 @@ public class Pazpar2ResponseData implements Serializable { } } - public boolean isError () { - return (getOneElement("error") != null); + public boolean hasApplicationError () { + return (getOneElement("applicationerror") != null); } - public String getErrorMessage() { - return getOneElementValue("error"); + public ApplicationError getApplicationError() { + return (ApplicationError) getOneElement("applicationerror"); } - + + + } diff --git a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/Pazpar2ResponseParser.java b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/Pazpar2ResponseParser.java index c4a4d11..40cf412 100644 --- a/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/Pazpar2ResponseParser.java +++ b/src/main/java/com/indexdata/pz2utils4jsf/pazpar2/data/Pazpar2ResponseParser.java @@ -52,6 +52,10 @@ public class Pazpar2ResponseParser extends DefaultHandler { } } + public static Pazpar2ResponseParser getParser() { + return new Pazpar2ResponseParser(); + } + private void initSax() throws ParserConfigurationException, SAXException { SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setNamespaceAware(true); @@ -60,7 +64,14 @@ public class Pazpar2ResponseParser extends DefaultHandler { xmlReader.setContentHandler(this); } - public Pazpar2ResponseData getObject (String response) { + /** + * Parses a Pazpar2 XML response -- or an error response as XML -- and produces a + * Pazpar2ResponseData object, i.e. a 'show' object + * + * @param response XML response string from Pazpar2 + * @return Response data object + */ + public Pazpar2ResponseData getDataObject (String response) { try { xmlReader.parse(new InputSource(new ByteArrayInputStream(response.getBytes("UTF-8")))); } catch (UnsupportedEncodingException e) { @@ -111,6 +122,8 @@ public class Pazpar2ResponseParser extends DefaultHandler { currentElement = new RecordResponse(); } else if (localName.equals("search")) { currentElement = new SearchResponse(); + } else if (localName.equals("applicationerror")) { + currentElement = new ApplicationError(); } else { currentElement = new Pazpar2ResponseData(); } diff --git a/src/main/java/com/indexdata/pz2utils4jsf/utils/Utils.java b/src/main/java/com/indexdata/pz2utils4jsf/utils/Utils.java index e17c224..5273134 100644 --- a/src/main/java/com/indexdata/pz2utils4jsf/utils/Utils.java +++ b/src/main/java/com/indexdata/pz2utils4jsf/utils/Utils.java @@ -2,6 +2,8 @@ package com.indexdata.pz2utils4jsf.utils; public class Utils { + public static String nl = System.getProperty("line.separator"); + public static String objectId(Object o) { int lastdot = o.toString().lastIndexOf('.'); if (lastdot>-1 && lastdot+1