From 678ffebe4cf5d43e910204c33427c74db5a843e7 Mon Sep 17 00:00:00 2001 From: Sonia Zorba Date: Fri, 25 Mar 2016 16:34:49 +0100 Subject: [PATCH] Added UCD manual insertion and its validation --- .../businesslayer/SearchUCD.java | 215 ++++++++++++++++++ .../businesslayer/UCDInfo.java | 43 ++++ .../businesslayer/UCDServiceException.java | 14 ++ .../webapp/CustomPartialResponseWriter.java | 5 + .../webapp/SearchUCDDialog.java | 195 +++++++--------- .../webapp/TapSchemaEditingBean.java | 22 +- .../src/main/webapp/WEB-INF/web.xml | 9 + .../src/main/webapp/expired.xhtml | 18 +- .../webapp/resources/js/edit-tapschema.js | 26 ++- .../src/main/webapp/tapSchemaEditing.xhtml | 127 +++++++---- .../tapschemamanager/datalayer/DLUtil.java | 8 + .../datalayer/DataProvider.java | 9 +- .../datalayer/TableEntity.java | 2 +- .../datalayer/TapSchemaHandler.java | 11 +- 14 files changed, 523 insertions(+), 181 deletions(-) create mode 100644 TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/businesslayer/SearchUCD.java create mode 100644 TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/businesslayer/UCDInfo.java create mode 100644 TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/businesslayer/UCDServiceException.java diff --git a/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/businesslayer/SearchUCD.java b/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/businesslayer/SearchUCD.java new file mode 100644 index 0000000..6436afc --- /dev/null +++ b/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/businesslayer/SearchUCD.java @@ -0,0 +1,215 @@ +package it.inaf.oats.ia2.tapschemamanager.businesslayer; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import javax.naming.InitialContext; +import javax.naming.NamingException; + +/** + * Collection of static methods for accessing to the UCD REST service and + * validating custom UCD. + * + * @author Sonia Zorba + */ +public class SearchUCD { + + private static final String UCD_SERVICE_PATH; + private static final String UCD_NOT_FOUND = "**** Could not find UCD ****"; + + public static final String REG_EXP_UCD; + public static final String REG_EXP_START_WITH_NAMESPACE; + + private static final Pattern PATTERN_UCD; + private static final Pattern PATTERN_START_WITH_NAMESPACE; + + static { + try { + InitialContext ic = new InitialContext(); + UCD_SERVICE_PATH = (String) ic.lookup("java:comp/env/UCD_SERVICE_PATH"); + } catch (NamingException e) { + throw new RuntimeException("Unable to find UCD service path in web.xml configuration!"); + } + + String namespaceRegExpPart = "[a-zA-Z]+\\:"; // validate e.g. "mynamespace:" + + String[] ucdCategories = new String[]{"arith", "em", "instr", "meta", "obs", "phot", "phys", "pos", "spect", "src", "stat", "time"}; + + StringBuilder sbCategories = new StringBuilder(); + boolean first = true; + for (String category : ucdCategories) { + if (!first) { + sbCategories.append("|"); + } + first = false; + sbCategories.append(category); + } + + String regExpWordPart = String.format("(%s)([\\.][\\-a-zA-Z0-9]+)*", sbCategories.toString()); // validate single word + + REG_EXP_UCD = String.format("^(%s)?%s(;%s)*$", namespaceRegExpPart, regExpWordPart, regExpWordPart); + REG_EXP_START_WITH_NAMESPACE = String.format("^%s.+$", namespaceRegExpPart); + + PATTERN_UCD = Pattern.compile(REG_EXP_UCD); + PATTERN_START_WITH_NAMESPACE = Pattern.compile(REG_EXP_START_WITH_NAMESPACE); + } + + private static String encodeText(String searchText) throws UCDServiceException { + try { + return URLEncoder.encode(searchText, "UTF-8").replace("+", "%20"); + } catch (UnsupportedEncodingException e) { + throw new UCDServiceException("Error while encoding input text"); + } + } + + public static String assign(String searchText) throws UCDServiceException { + searchText = encodeText(searchText); + String urlStr = UCD_SERVICE_PATH + "assign?value=" + searchText; + + try { + URL urlAssign = new URL(urlStr); + HttpURLConnection connectionAssign = (HttpURLConnection) urlAssign.openConnection(); + + int responseCodeAssign = connectionAssign.getResponseCode(); + if (responseCodeAssign == 200) { + BufferedReader br; + String line, response = ""; + + br = new BufferedReader(new InputStreamReader(connectionAssign.getInputStream())); + while ((line = br.readLine()) != null) { + response += line; + } + if (response.equals(UCD_NOT_FOUND)) { + return null; + } + + return response; + } + + throw new UCDServiceException("Server responded with a status of " + responseCodeAssign); + } catch (MalformedURLException e) { + throw new UCDServiceException("Malformed url: " + urlStr); + } catch (IOException e) { + throw new UCDServiceException("Error while reading server response"); + } + } + + public static List suggest(String searchText) throws UCDServiceException { + searchText = encodeText(searchText); + String urlStr = UCD_SERVICE_PATH + "suggest?value=" + searchText; + + try { + URL urlSuggest = new URL(urlStr); + + HttpURLConnection connectionSuggest = (HttpURLConnection) urlSuggest.openConnection(); + + int responseCodeSuggest = connectionSuggest.getResponseCode(); + if (responseCodeSuggest == 200) { + List resultList = new ArrayList(); + BufferedReader br = new BufferedReader(new InputStreamReader(connectionSuggest.getInputStream())); + String line; + + while ((line = br.readLine()) != null) { + String[] split1 = line.split("\\|"); + if (split1.length == 2) { + String score = split1[0].trim(); + String[] split2 = split1[1].trim().split(" "); + if (split2.length == 2) { + String flag = split2[0]; + String word = split2[1]; + resultList.add(new UCDInfo(score, flag, word)); + } + } + } + + return resultList; + } else if (responseCodeSuggest == 204) { + return null; + } + + throw new UCDServiceException("Server responded with a status of " + responseCodeSuggest); + } catch (MalformedURLException e) { + throw new UCDServiceException("Malformed url: " + urlStr); + } catch (IOException e) { + throw new UCDServiceException("Error while reading server response"); + } + } + + public static void explain(UCDInfo ucdInfo) throws UCDServiceException { + String searchText = encodeText(ucdInfo.getWord()); + String urlStr = UCD_SERVICE_PATH + "explain?value=" + searchText; + + try { + URL url = new URL(urlStr); + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + int responseCode = connection.getResponseCode(); + if (responseCode == 200) { + BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String line, response = ""; + while ((line = br.readLine()) != null) { + response += line; + } + + ucdInfo.setDefinition(response); + } else { + throw new UCDServiceException("Server responded with a status of " + responseCode); + } + } catch (MalformedURLException e) { + throw new UCDServiceException("Malformed url: " + urlStr); + } catch (IOException e) { + throw new UCDServiceException("Error while reading server response"); + } + } + + public static boolean validateManualUCD(String inputText) throws UCDServiceException { + if (inputText == null) { + return false; + } + + if (!PATTERN_UCD.matcher(inputText).matches()) { + return false; + } + + boolean customUCD = PATTERN_START_WITH_NAMESPACE.matcher(inputText).matches(); + + if (customUCD) { + return true; + } else { + String searchText = encodeText(inputText); + String urlStr = UCD_SERVICE_PATH + "validate?value=" + searchText; + + try { + URL url = new URL(urlStr); + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + int responseCode = connection.getResponseCode(); + if (responseCode == 200) { + BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String line, response = ""; + while ((line = br.readLine()) != null) { + response += line; + } + + return response.equals("0"); // String "0" means valid UCD + } else { + throw new UCDServiceException("Server responded with a status of " + responseCode); + } + } catch (MalformedURLException e) { + throw new UCDServiceException("Malformed url: " + urlStr); + } catch (IOException e) { + throw new UCDServiceException("Error while reading server response"); + } + } + } +} diff --git a/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/businesslayer/UCDInfo.java b/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/businesslayer/UCDInfo.java new file mode 100644 index 0000000..62f27aa --- /dev/null +++ b/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/businesslayer/UCDInfo.java @@ -0,0 +1,43 @@ +package it.inaf.oats.ia2.tapschemamanager.businesslayer; + +import java.io.Serializable; + +/** + * + * @author Sonia Zorba + */ +public class UCDInfo implements Serializable { + + private static final long serialVersionUID = 585936072742567972L; + + private final String score; + private final String flag; + private final String word; + private String definition; + + UCDInfo(String score, String flag, String word) { + this.score = score; + this.flag = flag; + this.word = word; + } + + public String getScore() { + return score; + } + + public String getFlag() { + return flag; + } + + public String getWord() { + return word; + } + + public String getDefinition() { + return definition; + } + + public void setDefinition(String definition) { + this.definition = definition; + } +} diff --git a/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/businesslayer/UCDServiceException.java b/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/businesslayer/UCDServiceException.java new file mode 100644 index 0000000..51d450e --- /dev/null +++ b/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/businesslayer/UCDServiceException.java @@ -0,0 +1,14 @@ +package it.inaf.oats.ia2.tapschemamanager.businesslayer; + +/** + * + * @author Sonia Zorba + */ +public class UCDServiceException extends Exception { + + private static final long serialVersionUID = 5009586704294107944L; + + public UCDServiceException(String message) { + super(message); + } +} diff --git a/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/webapp/CustomPartialResponseWriter.java b/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/webapp/CustomPartialResponseWriter.java index bc1c5df..f691eda 100644 --- a/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/webapp/CustomPartialResponseWriter.java +++ b/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/webapp/CustomPartialResponseWriter.java @@ -65,6 +65,11 @@ public class CustomPartialResponseWriter extends PartialResponseWriter { customJSUpdates.put(componentId, updateHandler); } + public void addCustomJSUpdate(JSUpdateHandler updateHandler) { + String sourceComponentId = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("javax.faces.source"); + addCustomJSUpdate(sourceComponentId, updateHandler); + } + public static CustomPartialResponseWriter getCurrentInstance() { return (CustomPartialResponseWriter) FacesContext.getCurrentInstance().getPartialViewContext().getPartialResponseWriter(); } diff --git a/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/webapp/SearchUCDDialog.java b/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/webapp/SearchUCDDialog.java index 2f3a8b9..d53308b 100644 --- a/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/webapp/SearchUCDDialog.java +++ b/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/webapp/SearchUCDDialog.java @@ -1,14 +1,16 @@ package it.inaf.oats.ia2.tapschemamanager.webapp; -import java.io.BufferedReader; -import java.io.InputStreamReader; +import it.inaf.oats.ia2.tapschemamanager.businesslayer.SearchUCD; +import it.inaf.oats.ia2.tapschemamanager.businesslayer.UCDInfo; +import it.inaf.oats.ia2.tapschemamanager.businesslayer.UCDServiceException; import java.io.Serializable; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import javax.enterprise.context.Dependent; +import javax.faces.application.FacesMessage; +import javax.faces.component.UIComponent; +import javax.faces.context.FacesContext; +import javax.faces.validator.ValidatorException; /** * @@ -19,50 +21,16 @@ public class SearchUCDDialog implements Serializable { private static final long serialVersionUID = -3503024742241865133L; - static final transient String UCD_REST_SERVICE = "http://localhost:8080/ucd/"; - static final transient String UCD_NOT_FOUND = "**** Could not find UCD ****"; + private boolean manualInsertion; - public static class UCDInfo implements Serializable { - - private static final long serialVersionUID = 585936072742567972L; - - private final String score; - private final String flag; - private final String word; - private String definition; - - UCDInfo(String score, String flag, String word) { - this.score = score; - this.flag = flag; - this.word = word; - } - - public String getScore() { - return score; - } - - public String getFlag() { - return flag; - } - - public String getWord() { - return word; - } - - public String getDefinition() { - return definition; - } - - public void setDefinition(String definition) { - this.definition = definition; - } - } + private String UCDManualText; private String description; private boolean UCDnotFound; + private String UCDServiceErrorMessage; private String selectedUCD; private String suggestedUCD; - private final List suggestedUCDs; + private List suggestedUCDs; public SearchUCDDialog() { suggestedUCDs = new ArrayList(); @@ -92,95 +60,106 @@ public class SearchUCDDialog implements Serializable { return suggestedUCDs; } - public void setDefault() { + public void setDefault() { + UCDManualText = null; + description = null; UCDnotFound = false; selectedUCD = null; suggestedUCD = null; suggestedUCDs.clear(); + + UCDServiceErrorMessage = null; } - public void search(String description) throws Exception { - setDefault(); - this.description = description; - - String searchText = URLEncoder.encode(this.description, "UTF-8").replace("+", "%20"); + public void search(String description) { + try { + setDefault(); + this.description = description; - // Search assign value - URL urlAssign = new URL(UCD_REST_SERVICE + "assign?value=" + searchText); - - HttpURLConnection connectionAssign = (HttpURLConnection) urlAssign.openConnection(); - - int responseCodeAssign = connectionAssign.getResponseCode(); - if (responseCodeAssign == 200) { - BufferedReader br; - String line, response = ""; - - br = new BufferedReader(new InputStreamReader(connectionAssign.getInputStream())); - while ((line = br.readLine()) != null) { - response += line; - } - if (response.equals(UCD_NOT_FOUND)) { + String assignResponse = SearchUCD.assign(description); + if (assignResponse == null) { UCDnotFound = true; } else { - selectedUCD = response; - suggestedUCD = response; - - // Search suggested values - URL urlSuggest = new URL(UCD_REST_SERVICE + "suggest?value=" + searchText); - - HttpURLConnection connectionSuggest = (HttpURLConnection) urlSuggest.openConnection(); - - int responseCodeSuggest = connectionSuggest.getResponseCode(); - if (responseCodeSuggest == 200) { - br = new BufferedReader(new InputStreamReader(connectionSuggest.getInputStream())); - while ((line = br.readLine()) != null) { - String[] split1 = line.split("\\|"); - if (split1.length == 2) { - String score = split1[0].trim(); - String[] split2 = split1[1].trim().split(" "); - if (split2.length == 2) { - String flag = split2[0]; - String word = split2[1]; - suggestedUCDs.add(new UCDInfo(score, flag, word)); - } - } - } - } else if (responseCodeSuggest == 204) { + selectedUCD = assignResponse; + suggestedUCD = assignResponse; + + List suggestResponse = SearchUCD.suggest(description); + if (suggestResponse == null) { UCDnotFound = true; } else { - throw new Exception("Service error"); + suggestedUCDs = suggestResponse; } } - } else if (responseCodeAssign == 204) { - UCDnotFound = true; - } else { - throw new Exception("Service error"); + } catch (UCDServiceException e) { + setUCDServiceErrorMessage(e); } } - + public void selectUCD(String selectedUCD) { this.selectedUCD = selectedUCD; } - public void explain(UCDInfo ucdInfo) throws Exception { - System.out.println("explain: " + ucdInfo.getWord()); - String searchText = URLEncoder.encode(ucdInfo.getWord(), "UTF-8").replace("+", "%20"); - URL url = new URL(UCD_REST_SERVICE + "explain?value=" + searchText); + public void explain(UCDInfo ucdInfo) { + try { + SearchUCD.explain(ucdInfo); + } catch (UCDServiceException e) { + setUCDServiceErrorMessage(e); + } + } - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + public boolean isManualInsertion() { + return manualInsertion; + } - int responseCode = connection.getResponseCode(); - if (responseCode == 200) { - BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream())); - String line, response = ""; - while ((line = br.readLine()) != null) { - response += line; - } + public void setManualInsertion(boolean manualInsertion) { + this.manualInsertion = manualInsertion; + } + + public String getUCDServiceErrorMessage() { + return UCDServiceErrorMessage; + } + + private void setUCDServiceErrorMessage(UCDServiceException e) { + setDefault(); + UCDServiceErrorMessage = e.getMessage(); + } + + public String getUCDManualText() { + return UCDManualText; + } + + public void setUCDManualText(String UCDManualText) { + this.UCDManualText = UCDManualText; + } - ucdInfo.setDefinition(response); + public void validateManualUCD(FacesContext context, UIComponent inputComponent, Object value) { + String textValue = (String) value; + + String validatorMessage = null; + if (textValue == null || textValue.isEmpty()) { + validatorMessage = "UCD can't be null"; } else { - throw new Exception("Service error"); + try { + if (!SearchUCD.validateManualUCD(textValue)) { + validatorMessage = "Invalid UCD!"; + } + } catch (UCDServiceException e) { + setUCDServiceErrorMessage(e); + FacesContext.getCurrentInstance().validationFailed(); + } } + + if (validatorMessage != null) { + throw new ValidatorException(new FacesMessage(validatorMessage)); + } + } + + public String getUCDRegExp() { + return SearchUCD.REG_EXP_UCD; + } + + public String getNamespaceRegExp() { + return SearchUCD.REG_EXP_START_WITH_NAMESPACE; } } diff --git a/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/webapp/TapSchemaEditingBean.java b/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/webapp/TapSchemaEditingBean.java index cf3b2af..cd197e5 100644 --- a/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/webapp/TapSchemaEditingBean.java +++ b/TapSchemaManager/src/main/java/it/inaf/oats/ia2/tapschemamanager/webapp/TapSchemaEditingBean.java @@ -125,7 +125,23 @@ public class TapSchemaEditingBean implements Serializable { } public void saveUCD() { - tapSchema.getSelectedColumn().setUcd(searchUCDDialog.getSelectedUCD()); + if (!FacesContext.getCurrentInstance().isValidationFailed()) { + + if (searchUCDDialog.isManualInsertion()) { + tapSchema.getSelectedColumn().setUcd(searchUCDDialog.getUCDManualText()); + } else { + tapSchema.getSelectedColumn().setUcd(searchUCDDialog.getSelectedUCD()); + } + + // New UCD is set and we can notify the client to close the UCD Search modal dialog. + CustomPartialResponseWriter.getCurrentInstance().addCustomJSUpdate(new JSUpdateHandler() { + + @Override + public String getUpdate() { + return "true"; + } + }); + } } public void update() throws SQLException { @@ -143,9 +159,7 @@ public class TapSchemaEditingBean implements Serializable { public void textInputChanged(EntityWrapper entityWrapper, String key) { final boolean isChanged = entityWrapper.isChanged(key); - CustomPartialResponseWriter writer = CustomPartialResponseWriter.getCurrentInstance(); - String sourceComponentId = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("javax.faces.source"); - writer.addCustomJSUpdate(sourceComponentId, new JSUpdateHandler() { + CustomPartialResponseWriter.getCurrentInstance().addCustomJSUpdate(new JSUpdateHandler() { @Override public String getUpdate() { diff --git a/TapSchemaManager/src/main/webapp/WEB-INF/web.xml b/TapSchemaManager/src/main/webapp/WEB-INF/web.xml index dae5711..ad68f62 100644 --- a/TapSchemaManager/src/main/webapp/WEB-INF/web.xml +++ b/TapSchemaManager/src/main/webapp/WEB-INF/web.xml @@ -1,5 +1,10 @@ + + UCD_SERVICE_PATH + java.lang.String + http://localhost:8080/ucd/ + javax.faces.PROJECT_STAGE Development @@ -61,4 +66,8 @@ org.jboss.weld.context.NonexistentConversationException /faces/expired.xhtml?nocid=true + + javax.faces.application.ViewExpiredException + /faces/expired.xhtml?nocid=true + diff --git a/TapSchemaManager/src/main/webapp/expired.xhtml b/TapSchemaManager/src/main/webapp/expired.xhtml index 4f96788..88767cd 100644 --- a/TapSchemaManager/src/main/webapp/expired.xhtml +++ b/TapSchemaManager/src/main/webapp/expired.xhtml @@ -6,18 +6,16 @@ xmlns:ui="http://java.sun.com/jsf/facelets" > TapSchema Manager - Credentials insertion page - -
-
-
-
-
- - Your session has expired. Please log in again. -
+
+
+
+
+
+ + Your session has expired. Please again.
- +
\ No newline at end of file diff --git a/TapSchemaManager/src/main/webapp/resources/js/edit-tapschema.js b/TapSchemaManager/src/main/webapp/resources/js/edit-tapschema.js index a612f01..dab984d 100644 --- a/TapSchemaManager/src/main/webapp/resources/js/edit-tapschema.js +++ b/TapSchemaManager/src/main/webapp/resources/js/edit-tapschema.js @@ -37,8 +37,31 @@ } var COLUMNS_COMPONENT_ID = 'main:columns-list'; + var UCDRegExp; window.TSM = { + initUCDValidator: function (UCDRegExpStr) { + UCDRegExp = new RegExp(UCDRegExpStr); + }, + validateManualUCD: function (event) { + $clientValidationMessage = $('#ucd_search_form\\:ucd_validation_result'); + + var valid = UCDRegExp.test(event.target.value); + if (valid) { + $clientValidationMessage.empty(); + } else { + $clientValidationMessage.text('Invalid UCD!'); + } + + $('#ucd_search_form\\:save_ucd').prop('disabled', !valid); + }, + saveUCDCalled: eventHandlerFactory(function (srcElement, jsupdate) { + if (jsupdate !== null) { + $('#searchUCDModal').modal('hide'); + } + // Hide loading + $('.loading').addClass('hide'); + }), textInputChanged: eventHandlerFactory(function (srcElement, jsupdate) { $(srcElement).toggleClass('changed', jsupdate === 'true'); }), @@ -136,7 +159,8 @@ jsf.ajax.addOnError(function (error) { $('#errorModal .errorName').text(error.errorName); $('#errorModal .errorMessage').text(error.errorMessage); - if (error.errorName.indexOf('NonexistentConversationException') !== -1) { + if (error.errorName.indexOf('NonexistentConversationException') !== -1 + || error.errorName.indexOf('ViewExpiredException') !== -1) { // If conversation has expired reload the page to display error message location.reload(); } else { diff --git a/TapSchemaManager/src/main/webapp/tapSchemaEditing.xhtml b/TapSchemaManager/src/main/webapp/tapSchemaEditing.xhtml index 213082f..ece686d 100644 --- a/TapSchemaManager/src/main/webapp/tapSchemaEditing.xhtml +++ b/TapSchemaManager/src/main/webapp/tapSchemaEditing.xhtml @@ -8,6 +8,9 @@ Editing #{tapSchemaEditing.tapSchema.name} + @@ -379,69 +382,93 @@