From d7b983fa9f499d31b239a82d04d9df3245eaf3a6 Mon Sep 17 00:00:00 2001
From: pkupczyk <pkupczyk>
Date: Mon, 6 Aug 2012 09:53:59 +0000
Subject: [PATCH] SP-202 / BIS-122 : Webuis available from openBIS UI

SVN: 26303
---
 .../framework/DisplayTypeIDGenerator.java     |   2 +
 .../client/application/ui/AbstractViewer.java |  81 +++++++++
 .../web/client/dto/ApplicationInfo.java       |  21 +++
 .../web/server/AbstractClientService.java     |   9 +
 .../shared/basic/WebAppProperties.java        | 119 +++++++++++++
 .../basic/WebAppSortingAndCodeComparator.java |  67 +++++++
 .../shared/basic/WebAppsProperties.java       |  63 +++++++
 .../generic/shared/basic/dto/WebApp.java      | 136 ++++++++++++++
 .../shared/basic/dto/WebAppContext.java       |  43 +++++
 .../shared/basic/dto/WebAppProperty.java      |  43 +++++
 .../client/application/DemoSampleViewer.java  |   3 +-
 .../dataset/GenericDataSetViewer.java         |   8 +-
 .../experiment/GenericExperimentViewer.java   |  16 +-
 .../material/GenericMaterialViewer.java       |   5 +-
 .../sample/GenericSampleViewer.java           |   9 +-
 .../WebAppSortingAndCodeComparatorTest.java   |  86 +++++++++
 .../shared/basic/WebAppsPropertiesTest.java   | 141 +++++++++++++++
 .../WebAppsPropertiesTest__empty.properties   |   1 +
 ...rtiesTest__with_duplicated_code.properties |   4 +
 ...iesTest__with_incorrect_sorting.properties |   4 +
 ...opertiesTest__with_many_webapps.properties |   5 +
 ...PropertiesTest__with_one_webapp.properties |   9 +
 ...PropertiesTest__without_webapps.properties |   2 +
 .../generic/shared/basic/dto/WebAppTest.java  | 166 ++++++++++++++++++
 .../web/client/application/ProteinViewer.java |   3 +-
 25 files changed, 1025 insertions(+), 21 deletions(-)
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppProperties.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppSortingAndCodeComparator.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsProperties.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebApp.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebAppContext.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebAppProperty.java
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppSortingAndCodeComparatorTest.java
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest.java
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__empty.properties
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_duplicated_code.properties
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_incorrect_sorting.properties
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_many_webapps.properties
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_one_webapp.properties
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__without_webapps.properties
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebAppTest.java

diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/framework/DisplayTypeIDGenerator.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/framework/DisplayTypeIDGenerator.java
index f3cc80b8f11..e3b480d0744 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/framework/DisplayTypeIDGenerator.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/framework/DisplayTypeIDGenerator.java
@@ -106,6 +106,8 @@ public enum DisplayTypeIDGenerator implements IDisplayTypeIDGenerator
 
     MODULE_SECTION("module-section"),
 
+    WEBAPP_SECTION("webapp-section"),
+
     // -------------- Viewers (detail view)
 
     GENERIC_DATASET_VIEWER("generic_dataset_viewer"),
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AbstractViewer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AbstractViewer.java
index 24da8b5a011..24e0ffa7c83 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AbstractViewer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AbstractViewer.java
@@ -19,16 +19,19 @@ package ch.systemsx.cisd.openbis.generic.client.web.client.application.ui;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 import com.extjs.gxt.ui.client.Style.LayoutRegion;
 import com.extjs.gxt.ui.client.event.BaseEvent;
 import com.extjs.gxt.ui.client.event.ButtonEvent;
 import com.extjs.gxt.ui.client.event.Events;
 import com.extjs.gxt.ui.client.event.Listener;
+import com.extjs.gxt.ui.client.widget.Component;
 import com.extjs.gxt.ui.client.widget.ContentPanel;
 import com.extjs.gxt.ui.client.widget.Html;
 import com.extjs.gxt.ui.client.widget.Layout;
 import com.extjs.gxt.ui.client.widget.LayoutContainer;
+import com.extjs.gxt.ui.client.widget.Text;
 import com.extjs.gxt.ui.client.widget.button.Button;
 import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData;
 import com.extjs.gxt.ui.client.widget.layout.TableRowLayout;
@@ -40,14 +43,17 @@ import com.google.gwt.user.client.ui.Widget;
 
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.AbstractAsyncCallback;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.Dict;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.DisposableTabContent;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.IViewContext;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ManagedPropertySection;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.TabContent;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.AppEvents;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.framework.DisplayTypeIDGenerator;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.plugin.IModule;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.plugin.IModuleInitializationObserver;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.LinkRenderer;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.columns.framework.LinkExtractor;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.grid.IDisposableComponent;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.listener.OpenEntityDetailsTabClickListener;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.listener.OpenEntityEditorTabClickListener;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.listener.OpenExperimentBrowserTabClickListener;
@@ -55,16 +61,21 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.widget.
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.GWTUtils;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IDelegatedAction;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IMessageProvider;
+import ch.systemsx.cisd.openbis.generic.client.web.client.dto.ApplicationInfo;
 import ch.systemsx.cisd.openbis.generic.shared.basic.DeletionUtils;
 import ch.systemsx.cisd.openbis.generic.shared.basic.ICodeHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithPermId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithProperties;
+import ch.systemsx.cisd.openbis.generic.shared.basic.WebAppSortingAndCodeComparator;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseModificationKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityVisit;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Space;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebApp;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebAppContext;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.api.IManagedProperty;
 
 /**
@@ -409,6 +420,66 @@ public abstract class AbstractViewer<D extends IEntityInformationHolder> extends
         moduleSectionManager.initialize(container, entity);
     }
 
+    protected void attachWebAppsSections(final SectionsPanel container,
+            final IEntityInformationHolderWithProperties entity, WebAppContext context)
+    {
+        List<WebApp> webApps = new ArrayList<WebApp>();
+
+        // find web applications for the given context, entity kind and entity type
+        for (final WebApp webApp : getApplicationInfo().getWebapps())
+        {
+            if (webApp.matchesContext(context)
+                    && webApp.matchesEntity(entity.getEntityKind(), entity.getEntityType()))
+            {
+                webApps.add(webApp);
+            }
+        }
+
+        // sort web applications using sorting and code properties
+        Collections.sort(webApps, new WebAppSortingAndCodeComparator());
+
+        // attach web applications to the container
+        for (final WebApp webApp : webApps)
+        {
+            DisposableTabContent webAppTab =
+                    new DisposableTabContent(webApp.getLabel(), viewContext, entity)
+                        {
+                            @Override
+                            protected IDisposableComponent createDisposableContent()
+                            {
+                                return new IDisposableComponent()
+                                    {
+
+                                        @Override
+                                        public void update(
+                                                Set<DatabaseModificationKind> observedModifications)
+                                        {
+                                        }
+
+                                        @Override
+                                        public DatabaseModificationKind[] getRelevantModifications()
+                                        {
+                                            return new DatabaseModificationKind[0];
+                                        }
+
+                                        @Override
+                                        public Component getComponent()
+                                        {
+                                            return new Text(webApp.getCode());
+                                        }
+
+                                        @Override
+                                        public void dispose()
+                                        {
+                                        }
+                                    };
+                            }
+                        };
+            webAppTab.setIds(DisplayTypeIDGenerator.WEBAPP_SECTION);
+            container.addSection(webAppTab);
+        }
+    }
+
     protected TabContent createManagedPropertySection(final String header,
             final IEntityInformationHolder entity, final IManagedProperty managedProperty)
     {
@@ -526,4 +597,14 @@ public abstract class AbstractViewer<D extends IEntityInformationHolder> extends
         }
     }
 
+    protected IViewContext<?> getViewContext()
+    {
+        return viewContext;
+    }
+
+    protected ApplicationInfo getApplicationInfo()
+    {
+        return getViewContext().getModel().getApplicationInfo();
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/ApplicationInfo.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/ApplicationInfo.java
index c7ac282fa66..f91bd1f803f 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/ApplicationInfo.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/dto/ApplicationInfo.java
@@ -17,6 +17,7 @@
 package ch.systemsx.cisd.openbis.generic.client.web.client.dto;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -24,6 +25,7 @@ import com.google.gwt.user.client.rpc.IsSerializable;
 
 import ch.systemsx.cisd.openbis.generic.shared.basic.annotation.DoNotEscape;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.CustomImport;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebApp;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebClientConfiguration;
 
 /**
@@ -50,6 +52,8 @@ public final class ApplicationInfo implements IsSerializable
 
     private List<CustomImport> customImports;
 
+    private List<WebApp> webapps;
+
     public int getMaxResults()
     {
         return maxResults;
@@ -138,4 +142,21 @@ public final class ApplicationInfo implements IsSerializable
     {
         return customImports;
     }
+
+    public void setWebapps(List<WebApp> webapps)
+    {
+        this.webapps = webapps;
+    }
+
+    public List<WebApp> getWebapps()
+    {
+        if (webapps == null)
+        {
+            return Collections.emptyList();
+        } else
+        {
+            return webapps;
+        }
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractClientService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractClientService.java
index d9f5980f1cb..5a70b641070 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractClientService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/server/AbstractClientService.java
@@ -73,6 +73,7 @@ import ch.systemsx.cisd.openbis.generic.shared.Constants;
 import ch.systemsx.cisd.openbis.generic.shared.IServer;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.WebAppsProperties;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.BasicEntityType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.CustomImport;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DisplaySettings;
@@ -82,6 +83,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ReportRowModel;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModel;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelColumnHeader;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebApp;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebClientConfiguration;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SessionContextDTO;
 import ch.systemsx.cisd.openbis.generic.shared.util.ServerUtils;
@@ -451,6 +453,7 @@ public abstract class AbstractClientService implements IClientService,
         applicationInfo.setEnabledTechnologies(ServerUtils.extractSet(getServiceProperties()
                 .getProperty(Constants.ENABLED_TECHNOLOGIES_KEY)));
         applicationInfo.setCustomImports(extractCustomImportProperties());
+        applicationInfo.setWebapps(extractWebAppsProperties());
         applicationInfo.setArchivingConfigured(isArchivingConfigured());
         applicationInfo.setVersion(getVersion());
         return applicationInfo;
@@ -477,6 +480,12 @@ public abstract class AbstractClientService implements IClientService,
         return results;
     }
 
+    private List<WebApp> extractWebAppsProperties()
+    {
+        WebAppsProperties webAppsProperties = new WebAppsProperties(getServiceProperties());
+        return webAppsProperties.getWebApps();
+    }
+
     @Override
     public final List<CustomImport> getCustomImports()
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppProperties.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppProperties.java
new file mode 100644
index 00000000000..500517b6423
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppProperties.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.basic;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
+import ch.systemsx.cisd.common.utilities.PropertyParametersUtil;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebAppProperty;
+
+/**
+ * @author pkupczyk
+ */
+public class WebAppProperties
+{
+
+    private Properties properties;
+
+    public WebAppProperties(Properties properties)
+    {
+        if (properties == null)
+        {
+            throw new IllegalArgumentException("Properties cannot be null");
+        }
+        this.properties = properties;
+    }
+
+    public String getLabel()
+    {
+        return getPropertyStringValue(WebAppProperty.LABEL);
+    }
+
+    public Integer getSorting()
+    {
+        return getPropertyIntegerValue(WebAppProperty.SORTING);
+    }
+
+    public String[] getContexts()
+    {
+        return getPropertyStringValues(WebAppProperty.CONTEXTS);
+    }
+
+    public Map<EntityKind, String[]> getEntityTypes()
+    {
+        Map<EntityKind, String[]> entityTypes = new HashMap<EntityKind, String[]>();
+        entityTypes.put(EntityKind.EXPERIMENT,
+                getPropertyStringValues(WebAppProperty.EXPERIMENT_TYPES));
+        entityTypes.put(EntityKind.SAMPLE, getPropertyStringValues(WebAppProperty.SAMPLE_TYPES));
+        entityTypes
+                .put(EntityKind.DATA_SET, getPropertyStringValues(WebAppProperty.DATA_SET_TYPES));
+        entityTypes
+                .put(EntityKind.MATERIAL, getPropertyStringValues(WebAppProperty.MATERIAL_TYPES));
+        return entityTypes;
+    }
+
+    private String getPropertyStringValue(WebAppProperty property)
+    {
+        String value = properties.getProperty(property.getName());
+
+        if (value == null || value.trim().length() == 0)
+        {
+            return null;
+        } else
+        {
+            return value.trim();
+        }
+    }
+
+    private Integer getPropertyIntegerValue(WebAppProperty property)
+    {
+        String value = getPropertyStringValue(property);
+
+        if (value == null)
+        {
+            return null;
+        } else
+        {
+            try
+            {
+                return Integer.valueOf(value);
+            } catch (NumberFormatException e)
+            {
+                throw new ConfigurationFailureException("Illegal value of " + property.getName()
+                        + " web application property. Value: " + value + " is not a number.");
+            }
+        }
+    }
+
+    private String[] getPropertyStringValues(WebAppProperty property)
+    {
+        String value = getPropertyStringValue(property);
+
+        if (value == null)
+        {
+            return new String[0];
+        } else
+        {
+            return PropertyParametersUtil.parseItemisedProperty(value, property.getName());
+        }
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppSortingAndCodeComparator.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppSortingAndCodeComparator.java
new file mode 100644
index 00000000000..f789eb89e41
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppSortingAndCodeComparator.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.basic;
+
+import java.util.Comparator;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebApp;
+
+/**
+ * @author pkupczyk
+ */
+public class WebAppSortingAndCodeComparator implements Comparator<WebApp>
+{
+
+    @Override
+    public int compare(WebApp o1, WebApp o2)
+    {
+        int compareBySorting = compareWithNullsLast(o1.getSorting(), o2.getSorting());
+
+        if (compareBySorting == 0)
+        {
+            return compareWithNullsLast(o1.getCode(), o2.getCode());
+        } else
+        {
+            return compareBySorting;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> int compareWithNullsLast(Comparable<T> o1, Comparable<T> o2)
+    {
+        if (o1 == null)
+        {
+            if (o2 == null)
+            {
+                return 0;
+            } else
+            {
+                return 1;
+            }
+        } else
+        {
+            if (o2 == null)
+            {
+                return -1;
+            } else
+            {
+                return o1.compareTo((T) o2);
+            }
+        }
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsProperties.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsProperties.java
new file mode 100644
index 00000000000..e0c5f21dcb4
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsProperties.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.basic;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import ch.systemsx.cisd.common.utilities.PropertyParametersUtil;
+import ch.systemsx.cisd.common.utilities.PropertyParametersUtil.SectionProperties;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebApp;
+
+/**
+ * @author pkupczyk
+ */
+public class WebAppsProperties
+{
+
+    private Properties properties;
+
+    public WebAppsProperties(Properties properties)
+    {
+        if (properties == null)
+        {
+            throw new IllegalArgumentException("Properties cannot be null");
+        }
+        this.properties = properties;
+    }
+
+    public List<WebApp> getWebApps()
+    {
+        List<WebApp> webApps = new ArrayList<WebApp>();
+
+        SectionProperties[] webAppSectionsArray =
+                PropertyParametersUtil.extractSectionProperties(properties,
+                        BasicConstant.WEB_APPS_PROPERTY, false);
+
+        for (SectionProperties webAppSection : webAppSectionsArray)
+        {
+            WebAppProperties webAppProperties = new WebAppProperties(webAppSection.getProperties());
+            webApps.add(new WebApp(webAppSection.getKey(), webAppProperties.getLabel(),
+                    webAppProperties.getSorting(), webAppProperties.getContexts(), webAppProperties
+                            .getEntityTypes()));
+        }
+
+        return webApps;
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebApp.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebApp.java
new file mode 100644
index 00000000000..a795acd22cc
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebApp.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.basic.dto;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * Information about a web application.
+ * 
+ * @author pkupczyk
+ */
+public class WebApp implements Serializable
+{
+    private static final long serialVersionUID = ServiceVersionHolder.VERSION;
+
+    private String code;
+
+    private String label;
+
+    private Integer sorting;
+
+    private String[] contexts;
+
+    private Map<EntityKind, String[]> entityTypes;
+
+    // GWT
+    @SuppressWarnings("unused")
+    private WebApp()
+    {
+    }
+
+    public WebApp(String code)
+    {
+        this(code, null, null, null, null);
+    }
+
+    public WebApp(String code, String label, Integer sorting, String[] contexts,
+            Map<EntityKind, String[]> entityTypes)
+    {
+        if (code == null)
+        {
+            throw new IllegalArgumentException("Code cannot be null");
+        }
+        this.code = code;
+        this.label = label;
+        this.sorting = sorting;
+        this.contexts = contexts;
+        this.entityTypes = entityTypes;
+    }
+
+    public String getCode()
+    {
+        return code;
+    }
+
+    public String getLabel()
+    {
+        return label;
+    }
+
+    public Integer getSorting()
+    {
+        return sorting;
+    }
+
+    public String[] getContexts()
+    {
+        return contexts;
+    }
+
+    public Map<EntityKind, String[]> getEntityTypes()
+    {
+        return entityTypes;
+    }
+
+    public boolean matchesContext(WebAppContext context)
+    {
+        if (context == null)
+        {
+            throw new IllegalArgumentException("Context cannot be null");
+        }
+        return matches(contexts, context.getName());
+    }
+
+    public boolean matchesEntity(EntityKind entityKind, BasicEntityType entityType)
+    {
+        if (entityKind == null)
+        {
+            throw new IllegalArgumentException("Entity kind cannot be null");
+        }
+        if (entityType == null)
+        {
+            throw new IllegalArgumentException("Entity type cannot be null");
+        }
+
+        if (entityTypes == null || entityTypes.get(entityKind) == null)
+        {
+            return false;
+        } else
+        {
+            return matches(entityTypes.get(entityKind), entityType.getCode());
+        }
+    }
+
+    private boolean matches(String[] configuredValues, String checkedValue)
+    {
+        if (configuredValues == null)
+        {
+            return false;
+        }
+        for (String configuredValue : configuredValues)
+        {
+            if (checkedValue.matches(configuredValue))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
\ No newline at end of file
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebAppContext.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebAppContext.java
new file mode 100644
index 00000000000..beac2d08945
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebAppContext.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.basic.dto;
+
+/**
+ * Contexts in which web applications are available in OpenBIS.
+ * 
+ * @author pkupczyk
+ */
+public enum WebAppContext
+{
+
+    QUERIES_MENU("queries-menu"), EXPERIMENT_DETAILS_VIEW("experiment-details-view"),
+    SAMPLE_DETAILS_VIEW("sample-details-view"), DATA_SET_DETAILS_VIEW("data-set-details-view"),
+    MATERIAL_DETAILS_VIEW("material-details-view");
+
+    private final String name;
+
+    private WebAppContext(String name)
+    {
+        this.name = name;
+    }
+
+    public String getName()
+    {
+        return this.name;
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebAppProperty.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebAppProperty.java
new file mode 100644
index 00000000000..7b60438a18d
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebAppProperty.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.basic.dto;
+
+/**
+ * Properties for configuration of web applications in OpenBIS.
+ * 
+ * @author pkupczyk
+ */
+public enum WebAppProperty
+{
+
+    LABEL("label"), SORTING("sorting"), CONTEXTS("openbisui-contexts"), EXPERIMENT_TYPES(
+            "experiment-entity-types"), SAMPLE_TYPES("sample-entity-types"), DATA_SET_TYPES(
+            "data-set-entity-types"), MATERIAL_TYPES("material-entity-types");
+
+    private final String name;
+
+    private WebAppProperty(String name)
+    {
+        this.name = name;
+    }
+
+    public String getName()
+    {
+        return this.name;
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/demo/client/web/client/application/DemoSampleViewer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/demo/client/web/client/application/DemoSampleViewer.java
index 0f993687166..f80b72e3caa 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/demo/client/web/client/application/DemoSampleViewer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/demo/client/web/client/application/DemoSampleViewer.java
@@ -50,8 +50,9 @@ public final class DemoSampleViewer extends AbstractViewer<Sample>
         reloadAllData();
     }
 
+    @Override
     @SuppressWarnings("unchecked")
-    private IViewContext<IDemoClientServiceAsync> getViewContext()
+    protected IViewContext<IDemoClientServiceAsync> getViewContext()
     {
         return (IViewContext<IDemoClientServiceAsync>) viewContext;
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/dataset/GenericDataSetViewer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/dataset/GenericDataSetViewer.java
index da2fe5b6673..0f0999c5f50 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/dataset/GenericDataSetViewer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/dataset/GenericDataSetViewer.java
@@ -66,6 +66,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ISerializableComparable;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRowWithObject;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebAppContext;
 import ch.systemsx.cisd.openbis.plugin.generic.client.web.client.IGenericClientServiceAsync;
 
 /**
@@ -112,11 +113,6 @@ abstract public class GenericDataSetViewer extends AbstractViewerWithVerticalSpl
         this.processButtonHolder = new ProcessButtonHolder();
     }
 
-    private IViewContext<?> getViewContext()
-    {
-        return viewContext;
-    }
-
     abstract protected void loadDatasetInfo(TechId datasetTechId,
             AsyncCallback<ExternalData> asyncCallback);
 
@@ -297,6 +293,8 @@ abstract public class GenericDataSetViewer extends AbstractViewerWithVerticalSpl
 
         moduleSectionManager.initialize(container, dataset);
 
+        attachWebAppsSections(container, dataset, WebAppContext.DATA_SET_DETAILS_VIEW);
+
         return container;
     }
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/experiment/GenericExperimentViewer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/experiment/GenericExperimentViewer.java
index 6cc47ff1803..c2d5a365167 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/experiment/GenericExperimentViewer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/experiment/GenericExperimentViewer.java
@@ -21,6 +21,12 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
+import com.extjs.gxt.ui.client.widget.Component;
+import com.extjs.gxt.ui.client.widget.Html;
+import com.extjs.gxt.ui.client.widget.layout.BorderLayout;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.Widget;
+
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.AbstractAsyncCallback;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.AttachmentVersionsSection;
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.Dict;
@@ -49,15 +55,10 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.BasicEntityType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseModificationKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebAppContext;
 import ch.systemsx.cisd.openbis.plugin.generic.client.web.client.IGenericClientServiceAsync;
 import ch.systemsx.cisd.openbis.plugin.generic.client.web.client.application.AbstractEntityDataSetsSection;
 
-import com.extjs.gxt.ui.client.widget.Component;
-import com.extjs.gxt.ui.client.widget.Html;
-import com.extjs.gxt.ui.client.widget.layout.BorderLayout;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.Widget;
-
 /**
  * The <i>generic</i> experiment viewer.
  * 
@@ -202,6 +203,8 @@ public class GenericExperimentViewer extends AbstractViewerWithVerticalSplit<Exp
                     SectionsPanel rightPanel = layoutSections(rightPanelSectionsOrNull);
                     attachManagedPropertiesSections(rightPanel, experiment);
                     attachModuleSpecificSections(rightPanel, experiment);
+                    attachWebAppsSections(rightPanel, experiment,
+                            WebAppContext.EXPERIMENT_DETAILS_VIEW);
                     add(rightPanel, createRightBorderLayoutData());
                     layout();
                 }
@@ -368,4 +371,5 @@ public class GenericExperimentViewer extends AbstractViewerWithVerticalSplit<Exp
     {
         return localViewContext.getMessage(Dict.BUTTON_DELETE_EXPERIMENT);
     }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/material/GenericMaterialViewer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/material/GenericMaterialViewer.java
index d16b8024791..1ae06c3b8e8 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/material/GenericMaterialViewer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/material/GenericMaterialViewer.java
@@ -49,6 +49,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseModificationKin
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Material;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebAppContext;
 import ch.systemsx.cisd.openbis.plugin.generic.client.web.client.IGenericClientServiceAsync;
 import ch.systemsx.cisd.openbis.plugin.generic.client.web.client.application.PropertiesPanelUtils;
 
@@ -71,8 +72,6 @@ abstract public class GenericMaterialViewer extends AbstractViewerWithVerticalSp
 
     private PropertyGrid propertyGrid;
 
-    private final IViewContext<?> viewContext;
-
     protected final TechId materialId;
 
     public static DatabaseModificationAwareComponent create(
@@ -97,7 +96,6 @@ abstract public class GenericMaterialViewer extends AbstractViewerWithVerticalSp
     protected GenericMaterialViewer(final IViewContext<?> viewContext, final TechId materialId)
     {
         super(viewContext, createId(materialId));
-        this.viewContext = viewContext;
         this.materialId = materialId;
         setLayout(new BorderLayout());
     }
@@ -130,6 +128,7 @@ abstract public class GenericMaterialViewer extends AbstractViewerWithVerticalSp
         container.layout();
         attachManagedPropertiesSections(container, material);
         moduleSectionManager.initialize(container, material);
+        attachWebAppsSections(container, material, WebAppContext.MATERIAL_DETAILS_VIEW);
         return container;
     }
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/sample/GenericSampleViewer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/sample/GenericSampleViewer.java
index 7904b8edeba..609c3b69d21 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/sample/GenericSampleViewer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/generic/client/web/client/application/sample/GenericSampleViewer.java
@@ -62,6 +62,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleParentWithDerived;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebAppContext;
 import ch.systemsx.cisd.openbis.plugin.generic.client.web.client.IGenericClientServiceAsync;
 import ch.systemsx.cisd.openbis.plugin.generic.client.web.client.application.PropertiesPanelUtils;
 
@@ -127,11 +128,6 @@ abstract public class GenericSampleViewer extends AbstractViewerWithVerticalSpli
         extendToolBar();
     }
 
-    private IViewContext<?> getViewContext()
-    {
-        return viewContext;
-    }
-
     @Override
     protected void fillBreadcrumbWidgets(List<Widget> widgets)
     {
@@ -237,6 +233,9 @@ abstract public class GenericSampleViewer extends AbstractViewerWithVerticalSpli
         attachManagedPropertiesSections(container, generator);
 
         moduleSectionManager.initialize(container, generator);
+
+        attachWebAppsSections(container, generator, WebAppContext.SAMPLE_DETAILS_VIEW);
+
         return container;
     }
 
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppSortingAndCodeComparatorTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppSortingAndCodeComparatorTest.java
new file mode 100644
index 00000000000..24d2f811702
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppSortingAndCodeComparatorTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.basic;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.Assert;
+
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebApp;
+
+/**
+ * @author pkupczyk
+ */
+public class WebAppSortingAndCodeComparatorTest
+{
+
+    @Test
+    public void testCompareWithAllSortingValuesNotNull()
+    {
+        List<WebApp> webApps = new ArrayList<WebApp>();
+        webApps.add(new WebApp("webapp3", null, 3, null, null));
+        webApps.add(new WebApp("webapp1", null, 1, null, null));
+        webApps.add(new WebApp("webapp2", null, 2, null, null));
+        webApps.add(new WebApp("webapp4", null, 4, null, null));
+
+        Collections.sort(webApps, new WebAppSortingAndCodeComparator());
+
+        Assert.assertEquals("webapp1", webApps.get(0).getCode());
+        Assert.assertEquals("webapp2", webApps.get(1).getCode());
+        Assert.assertEquals("webapp3", webApps.get(2).getCode());
+        Assert.assertEquals("webapp4", webApps.get(3).getCode());
+    }
+
+    @Test
+    public void testCompareWithAllSortingValuesNull()
+    {
+        List<WebApp> webApps = new ArrayList<WebApp>();
+        webApps.add(new WebApp("webapp3", null, null, null, null));
+        webApps.add(new WebApp("webapp1", null, null, null, null));
+        webApps.add(new WebApp("webapp2", null, null, null, null));
+        webApps.add(new WebApp("webapp4", null, null, null, null));
+
+        Collections.sort(webApps, new WebAppSortingAndCodeComparator());
+
+        Assert.assertEquals("webapp1", webApps.get(0).getCode());
+        Assert.assertEquals("webapp2", webApps.get(1).getCode());
+        Assert.assertEquals("webapp3", webApps.get(2).getCode());
+        Assert.assertEquals("webapp4", webApps.get(3).getCode());
+    }
+
+    @Test
+    public void testCompareWithSomeSortingValuesNotNull()
+    {
+        List<WebApp> webApps = new ArrayList<WebApp>();
+        webApps.add(new WebApp("webapp3", null, 1, null, null));
+        webApps.add(new WebApp("webapp1", null, null, null, null));
+        webApps.add(new WebApp("webapp2", null, null, null, null));
+        webApps.add(new WebApp("webapp4", null, 2, null, null));
+
+        Collections.sort(webApps, new WebAppSortingAndCodeComparator());
+
+        Assert.assertEquals("webapp3", webApps.get(0).getCode());
+        Assert.assertEquals("webapp4", webApps.get(1).getCode());
+        Assert.assertEquals("webapp1", webApps.get(2).getCode());
+        Assert.assertEquals("webapp2", webApps.get(3).getCode());
+    }
+
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest.java
new file mode 100644
index 00000000000..2032666f6ec
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.basic;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+
+import junit.framework.Assert;
+
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.WebApp;
+
+/**
+ * @author pkupczyk
+ */
+public class WebAppsPropertiesTest
+{
+
+    @Test
+    public void testGetWebAppsWithEmptyPropertiesShouldReturnEmptyList()
+    {
+        WebAppsProperties properties = loadProperties("empty");
+        List<WebApp> webApps = properties.getWebApps();
+        Assert.assertEquals(0, webApps.size());
+    }
+
+    @Test
+    public void testGetWebAppsWithPropertiesWithoutWebAppsShouldReturnEmptyList()
+    {
+        WebAppsProperties properties = loadProperties("without_webapps");
+        List<WebApp> webApps = properties.getWebApps();
+        Assert.assertEquals(0, webApps.size());
+    }
+
+    @Test
+    public void testGetWebAppsWithPropertiesWithOneWebAppShouldReturnListWithOneWebApp()
+    {
+        WebAppsProperties properties = loadProperties("with_one_webapp");
+        List<WebApp> webApps = properties.getWebApps();
+        Assert.assertEquals(1, webApps.size());
+        WebApp webApp = webApps.get(0);
+        Assert.assertEquals("webapp1", webApp.getCode());
+        Assert.assertEquals("Web app 1", webApp.getLabel());
+        Assert.assertEquals(Integer.valueOf(100), webApp.getSorting());
+        Assert.assertTrue(Arrays.equals(new String[]
+            { "queries-menu", "experiment-details-view" }, webApp.getContexts()));
+        Assert.assertTrue(Arrays.equals(new String[]
+            { "EXP1", "EXP2" }, webApp.getEntityTypes().get(EntityKind.EXPERIMENT)));
+        Assert.assertTrue(Arrays.equals(new String[]
+            { "SAM1" }, webApp.getEntityTypes().get(EntityKind.SAMPLE)));
+        Assert.assertTrue(Arrays.equals(new String[] {},
+                webApp.getEntityTypes().get(EntityKind.DATA_SET)));
+        Assert.assertTrue(Arrays.equals(new String[] {},
+                webApp.getEntityTypes().get(EntityKind.MATERIAL)));
+    }
+
+    @Test
+    public void testGetWebAppsWithPropertiesWithManyWebAppShouldReturnListWithManyWebApps()
+    {
+        WebAppsProperties properties = loadProperties("with_many_webapps");
+        List<WebApp> webApps = properties.getWebApps();
+        Assert.assertEquals(2, webApps.size());
+
+        WebApp webApp1 = webApps.get(0);
+        Assert.assertEquals("webapp1", webApp1.getCode());
+        Assert.assertEquals("Web app 1", webApp1.getLabel());
+        Assert.assertNull(webApp1.getSorting());
+        Assert.assertTrue(Arrays.equals(new String[] {}, webApp1.getContexts()));
+        Assert.assertTrue(Arrays.equals(new String[] {},
+                webApp1.getEntityTypes().get(EntityKind.EXPERIMENT)));
+        Assert.assertTrue(Arrays.equals(new String[] {},
+                webApp1.getEntityTypes().get(EntityKind.SAMPLE)));
+        Assert.assertTrue(Arrays.equals(new String[] {},
+                webApp1.getEntityTypes().get(EntityKind.DATA_SET)));
+        Assert.assertTrue(Arrays.equals(new String[] {},
+                webApp1.getEntityTypes().get(EntityKind.MATERIAL)));
+
+        WebApp webApp2 = webApps.get(1);
+        Assert.assertEquals("webapp2", webApp2.getCode());
+        Assert.assertEquals("Web app 2", webApp2.getLabel());
+        Assert.assertNull(webApp2.getSorting());
+        Assert.assertTrue(Arrays.equals(new String[] {}, webApp2.getContexts()));
+        Assert.assertTrue(Arrays.equals(new String[] {},
+                webApp2.getEntityTypes().get(EntityKind.EXPERIMENT)));
+        Assert.assertTrue(Arrays.equals(new String[] {},
+                webApp2.getEntityTypes().get(EntityKind.SAMPLE)));
+        Assert.assertTrue(Arrays.equals(new String[] {},
+                webApp2.getEntityTypes().get(EntityKind.DATA_SET)));
+        Assert.assertTrue(Arrays.equals(new String[] {},
+                webApp2.getEntityTypes().get(EntityKind.MATERIAL)));
+    }
+
+    @Test(expectedExceptions = ConfigurationFailureException.class)
+    public void testGetWebAppsWithPropertiesWithDuplicatedCodeShouldThrowException()
+    {
+        WebAppsProperties properties = loadProperties("with_duplicated_code");
+        properties.getWebApps();
+    }
+
+    @Test(expectedExceptions = ConfigurationFailureException.class)
+    public void testGetWebAppsWithPropertiesWithIncorrectSortingShouldThrowException()
+    {
+        WebAppsProperties properties = loadProperties("with_incorrect_sorting");
+        properties.getWebApps();
+    }
+
+    private WebAppsProperties loadProperties(String fileNameSuffix)
+    {
+        try
+        {
+            String fileName = getClass().getSimpleName() + "__" + fileNameSuffix + ".properties";
+            Properties properties = new Properties();
+            properties
+                    .load(new InputStreamReader(getClass().getResourceAsStream(fileName), "UTF-8"));
+            return new WebAppsProperties(properties);
+        } catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__empty.properties b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__empty.properties
new file mode 100644
index 00000000000..0035f5277c8
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__empty.properties
@@ -0,0 +1 @@
+abc = def
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_duplicated_code.properties b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_duplicated_code.properties
new file mode 100644
index 00000000000..18de2f7cdc0
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_duplicated_code.properties
@@ -0,0 +1,4 @@
+someProperty1 = someValue12, someValue12
+webapps = webapp1, webapp1
+webapp1.label = Web app 1
+someProperty2 = someValue2
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_incorrect_sorting.properties b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_incorrect_sorting.properties
new file mode 100644
index 00000000000..6ef4e9b710b
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_incorrect_sorting.properties
@@ -0,0 +1,4 @@
+someProperty1 = someValue12, someValue12
+webapps = webapp1
+webapp1.sorting = abc
+someProperty2 = someValue2
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_many_webapps.properties b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_many_webapps.properties
new file mode 100644
index 00000000000..3952dba36be
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_many_webapps.properties
@@ -0,0 +1,5 @@
+someProperty1 = someValue12, someValue12
+webapps = webapp1, webapp2
+webapp1.label = Web app 1
+webapp2.label = Web app 2
+someProperty2 = someValue2
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_one_webapp.properties b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_one_webapp.properties
new file mode 100644
index 00000000000..853d0629869
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__with_one_webapp.properties
@@ -0,0 +1,9 @@
+someProperty1 = someValue12, someValue12
+webapps = webapp1
+webapp1.label = Web app 1
+webapp1.sorting = 100
+webapp1.openbisui-contexts = queries-menu, experiment-details-view
+webapp1.experiment-entity-types = EXP1, EXP2
+webapp1.sample-entity-types = SAM1
+webapp1.data-set-entity-types =    
+someProperty2 = someValue2
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__without_webapps.properties b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__without_webapps.properties
new file mode 100644
index 00000000000..55e4d353d9e
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/WebAppsPropertiesTest__without_webapps.properties
@@ -0,0 +1,2 @@
+someProperty1 = someValue12, someValue12
+someProperty2 = someValue2
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebAppTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebAppTest.java
new file mode 100644
index 00000000000..b27786c8709
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/WebAppTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.basic.dto;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.testng.annotations.Test;
+
+/**
+ * @author pkupczyk
+ */
+public class WebAppTest
+{
+
+    @Test
+    public void testMatchesContextWithNullContextsList()
+    {
+        WebApp webApp = new WebApp("webapp", null, null, null, null);
+
+        for (WebAppContext context : WebAppContext.values())
+        {
+            Assert.assertFalse(webApp.matchesContext(context));
+        }
+    }
+
+    @Test
+    public void testMatchesContextWithEmptyContextsList()
+    {
+        WebApp webApp = new WebApp("webapp", null, null, new String[] {}, null);
+
+        for (WebAppContext context : WebAppContext.values())
+        {
+            Assert.assertFalse(webApp.matchesContext(context));
+        }
+    }
+
+    @Test
+    public void testMatchesContextWithExactMatchInContextsList()
+    {
+        WebApp webApp = new WebApp("webapp", null, null, new String[]
+            { "experiment-details-view" }, null);
+
+        for (WebAppContext context : WebAppContext.values())
+        {
+            if (WebAppContext.EXPERIMENT_DETAILS_VIEW.equals(context))
+            {
+                Assert.assertTrue(webApp.matchesContext(context));
+            } else
+            {
+                Assert.assertFalse(webApp.matchesContext(context));
+            }
+        }
+    }
+
+    @Test
+    public void testMatchesContextWithRegexpMatchInContextsList()
+    {
+        WebApp webApp = new WebApp("webapp", null, null, new String[]
+            { ".*-details-view" }, null);
+
+        for (WebAppContext context : WebAppContext.values())
+        {
+            if (WebAppContext.EXPERIMENT_DETAILS_VIEW.equals(context)
+                    || WebAppContext.SAMPLE_DETAILS_VIEW.equals(context)
+                    || WebAppContext.DATA_SET_DETAILS_VIEW.equals(context)
+                    || WebAppContext.MATERIAL_DETAILS_VIEW.equals(context))
+            {
+                Assert.assertTrue(webApp.matchesContext(context));
+            } else
+            {
+                Assert.assertFalse(webApp.matchesContext(context));
+            }
+        }
+    }
+
+    @Test
+    public void testMatchesEntityWithNullEntityTypesMap()
+    {
+        WebApp webApp = new WebApp("webapp", null, null, null, null);
+
+        for (EntityKind kind : EntityKind.values())
+        {
+            Assert.assertFalse(webApp.matchesEntity(kind, new BasicEntityType()));
+        }
+    }
+
+    @Test
+    public void testMatchesEntityWithEmptyEntityTypesMap()
+    {
+        Map<EntityKind, String[]> entityTypes = new HashMap<EntityKind, String[]>();
+        WebApp webApp = new WebApp("webapp", null, null, null, entityTypes);
+
+        for (EntityKind kind : EntityKind.values())
+        {
+            Assert.assertFalse(webApp.matchesEntity(kind, new BasicEntityType()));
+        }
+    }
+
+    @Test
+    public void testMatchesEntityWithExactMatchInEntityTypesMap()
+    {
+        Map<EntityKind, String[]> entityTypes = new HashMap<EntityKind, String[]>();
+        entityTypes.put(EntityKind.EXPERIMENT, new String[]
+            { "EXP0", "EXP1" });
+        WebApp webApp = new WebApp("webapp", null, null, null, entityTypes);
+
+        for (EntityKind kind : EntityKind.values())
+        {
+            BasicEntityType exp1Type = new BasicEntityType("EXP1");
+            BasicEntityType exp2Type = new BasicEntityType("EXP2");
+
+            if (EntityKind.EXPERIMENT.equals(kind))
+            {
+                Assert.assertTrue(webApp.matchesEntity(kind, exp1Type));
+                Assert.assertFalse(webApp.matchesEntity(kind, exp2Type));
+            } else
+            {
+                Assert.assertFalse(webApp.matchesEntity(kind, exp1Type));
+                Assert.assertFalse(webApp.matchesEntity(kind, exp2Type));
+            }
+        }
+    }
+
+    @Test
+    public void testMatchesEntityWithRegexpMatchInEntityTypesMap()
+    {
+        Map<EntityKind, String[]> entityTypes = new HashMap<EntityKind, String[]>();
+        entityTypes.put(EntityKind.EXPERIMENT, new String[]
+            { "EXP0", "EXP.*" });
+        WebApp webApp = new WebApp("webapp", null, null, null, entityTypes);
+
+        for (EntityKind kind : EntityKind.values())
+        {
+            BasicEntityType exp1Type = new BasicEntityType("EXP1");
+            BasicEntityType abc2Type = new BasicEntityType("ABC2");
+
+            if (EntityKind.EXPERIMENT.equals(kind))
+            {
+                Assert.assertTrue(webApp.matchesEntity(kind, exp1Type));
+                Assert.assertFalse(webApp.matchesEntity(kind, abc2Type));
+            } else
+            {
+                Assert.assertFalse(webApp.matchesEntity(kind, exp1Type));
+                Assert.assertFalse(webApp.matchesEntity(kind, abc2Type));
+            }
+        }
+    }
+
+}
diff --git a/rtd_phosphonetx/source/java/ch/systemsx/cisd/openbis/plugin/proteomics/client/web/client/application/ProteinViewer.java b/rtd_phosphonetx/source/java/ch/systemsx/cisd/openbis/plugin/proteomics/client/web/client/application/ProteinViewer.java
index 830fed860e3..412306860ad 100644
--- a/rtd_phosphonetx/source/java/ch/systemsx/cisd/openbis/plugin/proteomics/client/web/client/application/ProteinViewer.java
+++ b/rtd_phosphonetx/source/java/ch/systemsx/cisd/openbis/plugin/proteomics/client/web/client/application/ProteinViewer.java
@@ -157,8 +157,9 @@ public class ProteinViewer extends AbstractViewerWithVerticalSplit<IEntityInform
         reloadAllData();
     }
 
+    @Override
     @SuppressWarnings("unchecked")
-    private final IViewContext<IPhosphoNetXClientServiceAsync> getViewContext()
+    protected final IViewContext<IPhosphoNetXClientServiceAsync> getViewContext()
     {
         return (IViewContext<IPhosphoNetXClientServiceAsync>) viewContext;
     }
-- 
GitLab