package no.met.metadataeditor.view;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ComponentSystemEvent;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import no.met.metadataeditor.Config;
import no.met.metadataeditor.Editor;
import no.met.metadataeditor.EditorConfiguration;
import no.met.metadataeditor.EditorException;
import no.met.metadataeditor.EditorWidgetView;
import no.met.metadataeditor.LogUtils;
import no.met.metadataeditor.datastore.DataStore;
import no.met.metadataeditor.datastore.DataStoreFactory;
import no.met.metadataeditor.util.SkosUtils;
import no.met.metadataeditor.validationclient.ValidationClient;
import no.met.metadataeditor.validationclient.ValidationResponse;
import no.met.metadataeditor.widget.EditorWidget;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.primefaces.component.tabview.TabView;
import org.primefaces.event.TabChangeEvent;
/**
* Bean used to the hold the current state of the editor.
*
* The bean is in view scope.
*/
@ManagedBean
@ViewScoped
public class EditorBean implements Serializable {
private static final long serialVersionUID = 243543721833686400L;
private static final Logger logger = Logger.getLogger(EditorBean.class.getName());
private Editor editor;
// automatically set based on the query parameters
private String recordIdentifier;
// automatically set based on the query parameters
private String project;
// used to track the current active tab. We need this as some times the entire form is re-rendered and we lose
// the current tab wihtout this
private int activeTabId = 0;
boolean initPerformed = false;
private Map<String, String> skosKeywords;
public EditorBean() {
}
/**
* Initialise the the bean based on the "project" and "recordIdentifier".
* This method is automatically called in the preRenderView phase.
* @param event
* @throws IOException
*/
public void init(ComponentSystemEvent event) throws IOException {
if(!initPerformed){
validateProject(project);
validateRecordIdentifier(project, recordIdentifier);
editor = new Editor(project, recordIdentifier);
editor.init();
// need to get the session before the view is rendered to avoid getting exception.
// see http://stackoverflow.com/questions/7433575/cannot-create-a-session-after-the-response-has-been-committed
FacesContext.getCurrentInstance().getExternalContext().getSession(true);
initPerformed = true;
}
}
public void save() {
UserBean user = getUser();
if(user.isValidated()){
boolean success = editor.save(project, recordIdentifier, user.getUsername(), user.getPassword());
if( success ){
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Changes has been saved.", "Changes has been saved.");
FacesContext.getCurrentInstance().addMessage(null, msg);
// re-initialization to get possible new values generated by the widgets.
editor.init();
} else {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Failed to save changes.", "Failed to save changes.");
FacesContext.getCurrentInstance().addMessage(null, msg);
}
} else {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Login required before saving.", "Login required before saving.");
FacesContext.getCurrentInstance().addMessage(null, msg);
}
}
public void reset() throws IOException {
if( initPerformed ){
initPerformed = false;
init(null);
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "All changes have been reset.", "All changes have been reset.");
FacesContext.getCurrentInstance().addMessage(null, msg);
}
}
public void validate(){
DataStore datastore = DataStoreFactory.getInstance(project);
ValidationClient validationClient = datastore.getValidationClient(datastore.readMetadata(recordIdentifier));
if( validationClient == null ){
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "No validation configured for this format.", "No validation configured for this format.");
FacesContext.getCurrentInstance().addMessage(null, msg);
return;
}
String xmlContent = editor.editorContentToXML(project, recordIdentifier);
ValidationResponse validationResponse;
try {
validationResponse = validationClient.validate(xmlContent);
} catch (EditorException e){
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Exception happend during validation. Please check the logs", "Exception happend during validation. Please check the logs.");
FacesContext.getCurrentInstance().addMessage(null, msg);
LogUtils.logException(logger, "Exception happend during the validation.", e);
return;
}
if( validationResponse.success ){
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Validation successfull", "Validation successfull");
FacesContext.getCurrentInstance().addMessage(null, msg);
} else {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Validation failed.", validationResponse.message);
FacesContext.getCurrentInstance().addMessage("validation-messages", msg);
}
}
public void export() {
String editorContent = editor.export(project, recordIdentifier);
FacesContext ctx = FacesContext.getCurrentInstance();
final HttpServletResponse resp = (HttpServletResponse)ctx.getExternalContext().getResponse();
resp.setContentType("application/octet-stream");
resp.setHeader( "Content-Disposition", "attachment;filename=" + recordIdentifier + ".xml" );
try {
resp.getOutputStream().write(editorContent.getBytes());
resp.getOutputStream().flush();
resp.getOutputStream().close();
} catch (IOException e) {
throw new EditorException("Failed to write XML to response", e, EditorException.IO_ERROR);
}
ctx.responseComplete();
}
public EditorConfiguration getEditorConfiguration() {
return editor.getEditorConfiguration();
}
public String getRecordIdentifier() {
return recordIdentifier;
}
public void setRecordIdentifier(String recordIdentifier) {
this.recordIdentifier = recordIdentifier;
}
public String getProject() {
return project;
}
public void setProject(String project) {
this.project = project;
}
public int getActiveTabId() {
return activeTabId;
}
public void setActiveTabId(int activeTabId) {
this.activeTabId = activeTabId;
}
/**
* Track that the currently selected tab has changed.
* @param event
*/
public void tabChanged(TabChangeEvent event){
TabView tv = (TabView)event.getTab().getParent();
activeTabId = tv.getActiveIndex();
}
public void addValue(EditorWidget widget){
widget.addNewValue();
}
public void removeValue(EditorWidget widget, EditorWidgetView widgetView){
widget.removeValue(widgetView);
}
public List<String> getResourceValues(EditorWidget widget, String valueAttribute){
List<String> values = getResourceString(widget.getResourceUri().toString());
// if the current value is different from the one in the list we add it to the list of possible values
List<String> currentValues = getWidgetViewsAttributeValues(widget, valueAttribute);
for( String currValue : currentValues ){
if( !values.contains(currValue) ){
values.add(0, currValue);
}
}
return values;
}
/**
* @param widget The widget to check the value for.
* @param valueAttribute The name of the widget attribute where the relevant value is stored.
* @return True if the current value of the widget is found in the resource list. False otherwise.
*/
public boolean inResourceValues(EditorWidget widget, String valueAttribute) {
List<String> values = getResourceString(widget.getResourceUri().toString());
List<String> currentValues = getWidgetViewsAttributeValues(widget, valueAttribute);
for( String currValue : currentValues ){
if( !values.contains(currValue) ){
return false;
}
}
return true;
}
public List<String> getSkosResourceValues(EditorWidget widget){
if (skosKeywords == null) {
DataStore dataStore = DataStoreFactory.getInstance(project);
String resourceString = dataStore.readResource(widget.getResourceUri().toString());
skosKeywords = SkosUtils.getAllSkos(IOUtils.toInputStream(resourceString));
}
List<String> values = new ArrayList<>();
for (Map.Entry<String, String> skosKeyword : skosKeywords.entrySet()) {
values.add(skosKeyword.getValue());
}
return values;
}
public List<String> getSkosControlledVocabValues(EditorWidget widget){
DataStore dataStore = DataStoreFactory.getInstance(project);
String resourceString = dataStore.readResource(widget.getResourceUri().toString());
List<String> vocab = SkosUtils.getControlledVocab(IOUtils.toInputStream(resourceString));
// if the current value is different from the one in the list we add it to the list of possible values
List<String> currentValues = getWidgetViewsAttributeValues(widget, "listElement");
for( String currValue : currentValues ){
if( !vocab.contains(currValue) ){
// a null value in a select list caused a NPE at some occasions. Setting it to an empty string
// to avoid the problem
if( currValue == null ){
currValue = "";
}
vocab.add(0, currValue);
}
}
return vocab;
}
/**
* @param widget The widget to check the value for.
* @return True if the current value of the widget is found in the skos vocab. False otherwise.
*/
public boolean inSkosControlledVocab(EditorWidget widget) {
List<String> values = getSkosControlledVocabValues(widget);
List<String> currentValues = getWidgetViewsAttributeValues(widget, "listElement");
for( String currValue : currentValues ){
if( !values.contains(currValue) ){
return false;
}
}
return true;
}
public List<String> completeSkos(String query){
FacesContext context = FacesContext.getCurrentInstance();
EditorWidget widget = context.getApplication().evaluateExpressionGet(context, "#{widget}", EditorWidget.class);
List<String> keywords = getSkosResourceValues(widget);
List<String> currentValues = getWidgetViewsAttributeValues(widget, "listElement");
List<String> atcList = new ArrayList<>();
for (String keyword : keywords) {
if (StringUtils.containsIgnoreCase(keyword, query))
atcList.add(keyword);
}
//remove the already added values
for (String value : currentValues) {
atcList.remove(value);
}
return atcList;
}
/**
* Get the values from a resources as key values pairs. The keys and values
* are taken from alternate lines in the resource file.
* @param widget The widget to get resource values for
* @return A map of key/value pairs
*/
public Map<String,String> getKeyValueResourceValues(EditorWidget widget){
DataStore dataStore = DataStoreFactory.getInstance(project);
String resourceString = dataStore.readResource(widget.getResourceUri().toString());
List<String> resourceValues = new ArrayList<>(Arrays.asList(resourceString.split("\n")));
// ensure that we have an even number of elements.
if( resourceValues.size() % 2 != 0 ){
logger.log(Level.WARNING, "Odd number of lines in key/value resource. Even number expected");
resourceValues.add("");
}
Map<String, String> values = new LinkedHashMap<>();
for( int i = 0; i < resourceValues.size(); i += 2 ){
values.put(resourceValues.get(i), resourceValues.get(i+1));
}
return values;
}
private List<String> getWidgetViewsAttributeValues(EditorWidget widget, String attribute) {
List<String> currentValues = new ArrayList<>();
List<EditorWidgetView> widgetViews = widget.getWidgetViews();
for( EditorWidgetView view : widgetViews ){
currentValues.add(view.getValues().get(attribute));
}
return currentValues;
}
public List<String> getFilteredResourceValues(EditorWidget widget, String filterAttribute) {
List<String> currentValues = getWidgetViewsAttributeValues(widget, filterAttribute);
List<String> filteredValues = getResourceString(widget.getResourceUri().toString());
for( String value : currentValues ){
filteredValues.remove(value);
}
return filteredValues;
}
private List<String> getResourceString(String uri){
DataStore dataStore = DataStoreFactory.getInstance(project);
String resourceString = dataStore.readResource(uri);
String[] resourceValues = resourceString.split("\n");
List<String> values = new ArrayList<>();
for(String s : resourceValues ){
values.add(s);
}
return values;
}
/**
* Validates that the project parameter refers to an acutal project.
* @param context
* @param component
* @param object
* @throws IOException
*/
public void validateProject(String project) throws IOException {
Config config = new Config("/metadataeditor.properties", Config.ENV_NAME);
List<String> projects = config.getRequiredList("projects");
if( !projects.contains(project)){
String msg = "The project '" + project + "' has not been configured correctly. Please check that the project is correct.";
FacesContext.getCurrentInstance().getExternalContext().responseSendError(404, msg);
}
}
/**
* Validate that the record identifier exists
* @param context
* @param component
* @param object
* @throws IOException
*/
public void validateRecordIdentifier(String project, String recordIdentifier) throws IOException {
DataStore dataStore = DataStoreFactory.getInstance(project);
if( !dataStore.metadataExists(recordIdentifier)){
String msg = "The metadata record '" + recordIdentifier + "' does not exist for the project '" + project + "'. ";
msg += "Please check that both the record identifier and the project is correct";
FacesContext.getCurrentInstance().getExternalContext().responseSendError(404, msg);
}
}
/**
* @return The UserBean object for the current user.
*/
private UserBean getUser(){
// IMPLEMENTATION NOTE: This was first implemented as a @ManagedProperty, but that did not work
// for unknown reasons. It seemed like the UserBean object changed between request even if should
// stay the same. So this workaround was added instead.
HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
return (UserBean) request.getSession().getAttribute("userBean");
}
}