Commit f3ceaf25 by Juho Juopperi

Stuff

1 parent a6ffd3ff
Showing with 638 additions and 34 deletions
...@@ -4,3 +4,4 @@ V 20720323165551Z 02 unknown /C=FI/ST=Tampere/O=Bortal/CN=terminal ...@@ -4,3 +4,4 @@ V 20720323165551Z 02 unknown /C=FI/ST=Tampere/O=Bortal/CN=terminal
V 20720324125723Z 03 unknown /C=FI/ST=Tampere/O=Bortal/OU=Cashier/CN=cashier-01 V 20720324125723Z 03 unknown /C=FI/ST=Tampere/O=Bortal/OU=Cashier/CN=cashier-01
V 20720324125723Z 04 unknown /C=FI/ST=Tampere/O=Bortal/OU=Client/CN=client-01 V 20720324125723Z 04 unknown /C=FI/ST=Tampere/O=Bortal/OU=Client/CN=client-01
V 20720324125723Z 05 unknown /C=FI/ST=Tampere/O=Bortal/OU=Selfhelp/CN=selfhelp-01 V 20720324125723Z 05 unknown /C=FI/ST=Tampere/O=Bortal/OU=Selfhelp/CN=selfhelp-01
V 20720324161445Z 06 unknown /C=FI/ST=Tampere/O=Bortal/OU=Customer/CN=customer-01
...@@ -3,3 +3,4 @@ V 20720323165550Z 01 unknown /C=FI/ST=Tampere/O=Bortal/CN=bortal-server ...@@ -3,3 +3,4 @@ V 20720323165550Z 01 unknown /C=FI/ST=Tampere/O=Bortal/CN=bortal-server
V 20720323165551Z 02 unknown /C=FI/ST=Tampere/O=Bortal/CN=terminal V 20720323165551Z 02 unknown /C=FI/ST=Tampere/O=Bortal/CN=terminal
V 20720324125723Z 03 unknown /C=FI/ST=Tampere/O=Bortal/OU=Cashier/CN=cashier-01 V 20720324125723Z 03 unknown /C=FI/ST=Tampere/O=Bortal/OU=Cashier/CN=cashier-01
V 20720324125723Z 04 unknown /C=FI/ST=Tampere/O=Bortal/OU=Client/CN=client-01 V 20720324125723Z 04 unknown /C=FI/ST=Tampere/O=Bortal/OU=Client/CN=client-01
V 20720324125723Z 05 unknown /C=FI/ST=Tampere/O=Bortal/OU=Selfhelp/CN=selfhelp-01
...@@ -7,6 +7,7 @@ import javax.annotation.security.DeclareRoles; ...@@ -7,6 +7,7 @@ import javax.annotation.security.DeclareRoles;
import javax.ejb.EJB; import javax.ejb.EJB;
import javax.ejb.SessionContext; import javax.ejb.SessionContext;
import javax.ejb.Stateless; import javax.ejb.Stateless;
import javax.resource.spi.IllegalStateException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -68,7 +69,7 @@ import fi.insomnia.bortal.model.User; ...@@ -68,7 +69,7 @@ import fi.insomnia.bortal.model.User;
TerminalPermission.S_TERMINAL, TerminalPermission.S_TERMINAL,
TerminalPermission.S_CASHIER_TERMINAL, TerminalPermission.S_CASHIER_TERMINAL,
TerminalPermission.S_CLIENT_TERMINAL, TerminalPermission.S_CUSTOMER_TERMINAL,
TerminalPermission.S_SELFHELP_TERMINAL TerminalPermission.S_SELFHELP_TERMINAL
}) })
public class PermissionBean implements PermissionBeanLocal { public class PermissionBean implements PermissionBeanLocal {
...@@ -178,4 +179,20 @@ public class PermissionBean implements PermissionBeanLocal { ...@@ -178,4 +179,20 @@ public class PermissionBean implements PermissionBeanLocal {
return principalName; return principalName;
} }
@Override
public String getCommonName() throws IllegalStateException {
String dn = context.getCallerPrincipal().getName();
String[] parts = dn.split(",");
for (String part : parts) {
if (part.trim().toUpperCase().startsWith("CN=")) {
String cn = part.substring("CN=".length());
return cn;
}
}
throw new IllegalStateException("Current security principal has no CN");
}
} }
package fi.insomnia.bortal.beans;
import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateful;
import javax.resource.spi.IllegalStateException;
import fi.insomnia.bortal.facade.SalesEntityFacade;
import fi.insomnia.bortal.model.salespoint.SalesEntity;
import fi.insomnia.bortal.model.salespoint.Salespoint;
import fi.insomnia.bortal.salespoint.SalespointSessionManager;
/**
* Session Bean implementation class SalesEntitySessionBean
*/
@Stateful
@LocalBean
public class SalesEntitySessionBean implements SalesEntitySessionBeanLocal {
@EJB
PermissionBeanLocal permissionBean;
@EJB
SalespointContainerBeanLocal salespointContainer;
@EJB
SalesEntityFacade salesEntityFacade;
private SalespointSessionManager sessionManager = null;
public SalesEntitySessionBean() {
}
public SalespointSessionManager getSessionManager()
throws IllegalStateException {
if (sessionManager == null) {
String cn = permissionBean.getCommonName();
sessionManager = getSessionManagerByCommonName(cn);
}
return sessionManager;
}
private SalespointSessionManager getSessionManagerByCommonName(String cn) {
SalesEntity salesEntity = salesEntityFacade.findByCN(cn);
if (salesEntity == null)
throw new IllegalArgumentException(
"There is no SalesEntity with CN=" + cn);
Salespoint salespoint = salesEntity.getPoint();
if (salespoint == null)
throw new IllegalArgumentException("SalesEntity (CN=" + cn
+ ") is not associated with Salespoint");
return salespointContainer.getSessionManagerBySalespointId(salespoint
.getId());
}
}
package fi.insomnia.bortal.beans;
import java.util.HashMap;
import javax.ejb.LocalBean;
import javax.ejb.Singleton;
import fi.insomnia.bortal.salespoint.SalespointSessionManager;
/**
* Keeps track of active Salespoint sessions
*/
@Singleton
@LocalBean
public class SalespointContainerBean implements SalespointContainerBeanLocal {
// Salespoint.Id -> SalespointSessionManager
HashMap<Integer, SalespointSessionManager> sessionManagers = new HashMap<Integer, SalespointSessionManager>();
@Override
public SalespointSessionManager getSessionManagerBySalespointId(Integer id) {
if (!sessionManagers.containsKey(id)) {
sessionManagers.put(id, new SalespointSessionManager(id));
}
return sessionManagers.get(id);
}
}
package fi.insomnia.bortal.facade;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import fi.insomnia.bortal.model.salespoint.SalesEntity;
/**
* Session Bean implementation class SalesEntityFacade
*/
@Stateless
@LocalBean
public class SalesEntityFacade extends GenericFacade<Integer, SalesEntity>{
@PersistenceContext
private EntityManager em;
public SalesEntityFacade() {
super(SalesEntity.class);
}
@Override
protected EntityManager getEm() {
return em;
}
public SalesEntity findByCN(String cn) {
// TODO Auto-generated method stub
return null;
}
}
package fi.insomnia.bortal.beans; package fi.insomnia.bortal.beans;
import javax.ejb.Local; import javax.ejb.Local;
import javax.resource.spi.IllegalStateException;
import fi.insomnia.bortal.enums.apps.IAppPermission; import fi.insomnia.bortal.enums.apps.IAppPermission;
import fi.insomnia.bortal.model.User; import fi.insomnia.bortal.model.User;
...@@ -25,6 +26,13 @@ public interface PermissionBeanLocal { ...@@ -25,6 +26,13 @@ public interface PermissionBeanLocal {
User getAnonUser(); User getAnonUser();
String getPrincipal(); String getPrincipal();
/**
* Get common name of the logged in cert like "customer-01"
* @return CN of the certificate
* @throws IllegalStateException Principal has no CN
*/
String getCommonName() throws IllegalStateException;
// boolean hasPermission(String perm); // boolean hasPermission(String perm);
......
package fi.insomnia.bortal.beans;
import javax.ejb.Local;
import javax.resource.spi.IllegalStateException;
import fi.insomnia.bortal.salespoint.SalespointSessionManager;
@Local
public interface SalesEntitySessionBeanLocal {
SalespointSessionManager getSessionManager() throws IllegalStateException;
}
package fi.insomnia.bortal.beans;
import javax.ejb.Local;
import fi.insomnia.bortal.salespoint.SalespointSessionManager;
@Local
public interface SalespointContainerBeanLocal {
SalespointSessionManager getSessionManagerBySalespointId(Integer id);
}
package fi.insomnia.bortal.salespoint;
import java.util.ArrayList;
public class Cart {
ArrayList<CartItem> cartItems;
}
package fi.insomnia.bortal.salespoint;
import java.math.BigDecimal;
import fi.insomnia.bortal.model.Product;
public class CartItem {
Product product;
BigDecimal quantity;
public String getName() {
return "foo";
}
public BigDecimal getQuantity() {
return new BigDecimal(0);
}
public BigDecimal getUnitPrice() {
return new BigDecimal(0);
}
public BigDecimal getTotal() {
return getUnitPrice().multiply(getQuantity());
}
}
package fi.insomnia.bortal.salespoint;
import fi.insomnia.bortal.model.User;
public class SalesSession {
User cashierUser;
User customerUser;
Cart shoppingCart;
}
package fi.insomnia.bortal.salespoint;
import java.util.ArrayList;
import fi.insomnia.bortal.model.salespoint.SalesEntity;
import fi.insomnia.bortal.model.salespoint.Salespoint;
/**
* Manages sessions of one Salespoint
* @author jkj
*
*/
public class SalespointSessionManager {
Integer salespointId;
public SalespointSessionManager(Integer id) {
salespointId = id;
}
Salespoint salespoint;
SalesEntity cashier;
SalesEntity customer;
ArrayList<SalesSession> session;
}
...@@ -20,12 +20,27 @@ ...@@ -20,12 +20,27 @@
<!-- Vector VE (VENEZUELA, BOLIVARIAN REPUBLIC OF) --> <!-- Vector VE (VENEZUELA, BOLIVARIAN REPUBLIC OF) -->
<locale-config> <locale-config>
<default-locale>fi_FI</default-locale> <default-locale>fi_FI</default-locale>
<supported-locale>fi_fi_XII</supported-locale> <!-- <supported-locale>fi_fi_XII</supported-locale> <supported-locale>en_ST_v7</supported-locale> -->
<supported-locale>en_ST_v7</supported-locale>
</locale-config> </locale-config>
</application> </application>
<!--
<navigation-rule>
<from-view-id>/index.xhtml</from-view-id>
<navigation-case>
<from-outcome>redirect-to-cashier</from-outcome>
<to-view-id>cashier.wtf</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<from-outcome>redirect-to-client</from-outcome>
<to-view-id>customer.wtf</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
-->
<factory> <factory>
<exception-handler-factory>fi.insomnia.bortal.terminal.exceptions.BortalTerminalExceptionHandlerFactory</exception-handler-factory> <exception-handler-factory>fi.insomnia.bortal.terminal.exceptions.BortalTerminalExceptionHandlerFactory</exception-handler-factory>
</factory> </factory>
......
...@@ -68,17 +68,6 @@ ...@@ -68,17 +68,6 @@
</user-data-constraint> </user-data-constraint>
</security-constraint> </security-constraint>
<!--
<security-role>
<description>All authenticated users</description>
<role-name>allusers</role-name>
</security-role>
<security-role>
<description>Sales Terminal</description>
<role-name>terminal</role-name>
</security-role>
-->
<persistence-unit-ref> <persistence-unit-ref>
<persistence-unit-ref-name>BortalEMF</persistence-unit-ref-name> <persistence-unit-ref-name>BortalEMF</persistence-unit-ref-name>
</persistence-unit-ref> </persistence-unit-ref>
......
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:salespoint="http://java.sun.com/jsf/composite/salespoint">
<h:head>
<title></title>
</h:head>
<h:body>
<ui:composition template="/layout/#{sessionHandler.layout}.xhtml">
<f:metadata>
<f:event type="preRenderView" listener="#{cashierView.initCreateView}" />
</f:metadata>
<ui:define name="content">
<h1>Salespoint cashier mode</h1>
<p>#{testView.getPrincipal()}</p>
<salespoint:cart/>
</ui:define>
</ui:composition>
</h:body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:salespoint="http://java.sun.com/jsf/composite/salespoint">
<h:head>
<title></title>
</h:head>
<h:body>
<ui:composition template="/layout/#{sessionHandler.layout}.xhtml">
<f:metadata>
<f:event type="preRenderView"
listener="#{customerView.initCreateView}" />
</f:metadata>
<ui:define name="content">
<h1>Salespoint customer mode</h1>
<p>#{testView.getPrincipal()}</p>
<salespoint:cart/>
</ui:define>
</ui:composition>
</h:body>
</html>
...@@ -8,22 +8,14 @@ ...@@ -8,22 +8,14 @@
</h:head> </h:head>
<h:body> <h:body>
<ui:composition template="/layout/#{sessionHandler.layout}.xhtml"> <!-- Just redirect to /cashier or /customer -->
<ui:define name="content"> <f:metadata>
<f:event type="preRenderView"
listener="#{redirectView.redirectByPermissions()}" />
</f:metadata>
<h:outputLabel rendered="#{sessionHandler.isInDevelopmentMode()}"> <h1>What kind of certificate is that?</h1>
Development-tilassa.
Vaihda web.xml-tiedostosta ohjelman tila (javax.faces.PROJECT_STAGE) Productioniksi ennen kuin julkaiset ohjelman tuotantoon.
</h:outputLabel>
<h1>Hurrdurr</h1>
<p>abba #{request.contextPath} beef</p>
<p>foo #{testView.getPrincipal()} baz</p>
</ui:define>
</ui:composition>
</h:body> </h:body>
</html> </html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:tools="http://java.sun.com/jsf/composite/tools"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title></title>
</h:head>
<h:body>
<ui:composition template="/layout/#{sessionHandler.layout}.xhtml">
<ui:param name="thispage" value="page.permissionDenied" />
<ui:define name="content">
<h1>#{i18n['permissiondenied.header']}</h1>
<p>
<h:outputText rendered="#{!sessionHandler.isLoggedIn()}"
value="#{i18n['permissiondenied.notLoggedIn']}" />
<h:outputText rendered="#{sessionHandler.isLoggedIn()}"
value="#{i18n['permissiondenied.alreadyLoggedIn']}" />
</p>
</ui:define>
</ui:composition>
</h:body>
</html>
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<composite:interface>
</composite:interface>
<composite:implementation>
<h:dataTable styleClass="bordertable" id="items"
value="#{cartView.items}" var="cartItem">
<h:column>
<f:facet name="header">
<h:link value="#{i18n['cart.item']}" includeViewParams="true" />
</f:facet>
<h:outputText value="#{cartItem.name}" />
</h:column>
<h:column>
<f:facet name="header">
<h:link value="#{i18n['cart.item_quantity']}"
includeViewParams="true" />
</f:facet>
<h:outputText value="#{cartItem.quantity}" />
</h:column>
<h:column>
<f:facet name="header">
<h:link value="#{i18n['cart.item_unitprice']}"
includeViewParams="true" />
</f:facet>
<h:outputText value="#{cartItem.unitPrice}" />
</h:column>
<h:column>
<f:facet name="header">
<h:link value="#{i18n['cart.item_total']}"
includeViewParams="true" />
</f:facet>
<h:outputText value="#{cartItem.total}" />
</h:column>
</h:dataTable>
<div class="cart_total">
#{i18n['cart.total']} #{cartView.total}
</div>
</composite:implementation>
</html>
\ No newline at end of file
global.copyright=Copyright left right global.copyright=Lanisofta
\ No newline at end of file cart.item=Aihe
cart.item_quantity=Mr
cart.item_unitprice=Yksikkhinta
cart.item_total=Summa
cart.total=Kokonaissumma
package fi.insomnia.bortal.terminal.view;
import java.io.Serializable;
import javax.ejb.EJB;
import javax.enterprise.context.Conversation;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fi.insomnia.bortal.beans.EventBeanLocal;
import fi.insomnia.bortal.beans.PermissionBeanLocal;
import fi.insomnia.bortal.enums.apps.IAppPermission;
import fi.insomnia.bortal.model.EventPk;
import fi.insomnia.bortal.terminal.handler.NavigationHandler;
import fi.insomnia.bortal.utilities.I18n;
public abstract class AbstractView implements Serializable {
private static final long serialVersionUID = 4025432447507222251L;
@Inject
private Conversation conversation;
private static final Logger logger = LoggerFactory
.getLogger(AbstractView.class);
@Inject
protected NavigationHandler navihandler;
@EJB
protected PermissionBeanLocal permbean;
@EJB
private EventBeanLocal eventbean;
public void beginConversation() {
if (conversation.isTransient()) {
conversation.begin();
}
}
protected EventPk getPk(int id) {
return new EventPk(eventbean.getCurrentEvent().getId(), id);
}
public boolean hasPermission(IAppPermission perm) {
return permbean.hasPermission(perm);
}
protected boolean requirePermissions(IAppPermission perm,
boolean... externalChecks) {
boolean ret = requirePermissions(hasPermission(perm));
if (ret && externalChecks.length > 0) {
ret = requirePermissions(externalChecks);
}
if (!ret) {
logger.warn("Permission required failed for {}", perm.getFullName());
}
return ret;
}
protected boolean requirePermissions(boolean... externalChecks) {
boolean ret = true;
for (boolean check : externalChecks) {
if (!check) {
ret = false;
break;
}
}
if (!ret) {
FacesContext fcont = FacesContext.getCurrentInstance();
HttpServletRequest req = (HttpServletRequest) fcont
.getExternalContext().getRequest();
StringBuilder viewidbuilder = new StringBuilder().append(
req.getContextPath()).append(req.getServletPath());
if (req.getQueryString() != null) {
viewidbuilder.append("?").append(req.getQueryString());
}
navihandler.saveDestination(viewidbuilder.toString());
logger.debug("Permission denied. Saving navi {} for later use",
viewidbuilder.toString());
// navihandler.navigateTo("/permissionDenied");
fcont.getApplication().getNavigationHandler()
.handleNavigation(fcont, null, "/permissionDenied");
}
return ret;
}
protected void addFaceMessage(String string, Object... params) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(I18n.get(string, params)));
}
}
package fi.insomnia.bortal.terminal.view;
import java.math.BigDecimal;
import javax.ejb.EJB;
import javax.inject.Named;
import javax.resource.spi.IllegalStateException;
import fi.insomnia.bortal.beans.SalesEntitySessionBeanLocal;
import fi.insomnia.bortal.beans.SalespointContainerBeanLocal;
import fi.insomnia.bortal.salespoint.CartItem;
import fi.insomnia.bortal.salespoint.SalespointSessionManager;
@Named
public class CartView extends AbstractView {
private static final long serialVersionUID = 9041477262092320847L;
@EJB
SalesEntitySessionBeanLocal sessionBean;
public CartItem[] getItems() throws IllegalStateException {
SalespointSessionManager sessionManager = sessionBean.getSessionManager();
return new CartItem[] { new CartItem(), new CartItem() };
}
public BigDecimal getTotal() throws IllegalStateException {
CartItem[] cartItems = getItems();
BigDecimal result = new BigDecimal(0);
for (int i = 0; i < cartItems.length; i++) {
result = result.add(cartItems[i].getTotal());
}
return result;
}
}
package fi.insomnia.bortal.terminal.view;
import javax.inject.Named;
import fi.insomnia.bortal.enums.apps.TerminalPermission;
@Named
public class CashierView extends AbstractView {
private static final long serialVersionUID = -4415449134790807417L;
public void initCreateView() {
if (super.requirePermissions(TerminalPermission.CASHIER)) {
super.beginConversation();
}
}
}
package fi.insomnia.bortal.terminal.view;
import javax.inject.Named;
import fi.insomnia.bortal.enums.apps.TerminalPermission;
@Named
public class CustomerView extends AbstractView {
private static final long serialVersionUID = -8746462342138568821L;
public void initCreateView() {
if (super.requirePermissions(TerminalPermission.CUSTOMER)) {
super.beginConversation();
}
}
}
package fi.insomnia.bortal.terminal.view;
import javax.ejb.EJB;
import javax.faces.context.FacesContext;
import javax.inject.Named;
import fi.insomnia.bortal.beans.PermissionBeanLocal;
import fi.insomnia.bortal.enums.apps.TerminalPermission;
@Named
public class RedirectView {
@EJB
PermissionBeanLocal permissionBean;
public void redirectByPermissions() throws Exception {
FacesContext fcont = FacesContext.getCurrentInstance();
if (permissionBean.hasPermission(TerminalPermission.CASHIER)) {
fcont.getApplication().getNavigationHandler()
.handleNavigation(fcont, null, "/cashier");
} else if (permissionBean.hasPermission(TerminalPermission.CUSTOMER)) {
fcont.getApplication().getNavigationHandler()
.handleNavigation(fcont, null, "/customer");
} else {
throw new Exception("You have a funny type of client certificate");
}
}
}
...@@ -11,12 +11,12 @@ import fi.insomnia.bortal.enums.BortalApplication; ...@@ -11,12 +11,12 @@ import fi.insomnia.bortal.enums.BortalApplication;
*/ */
public enum TerminalPermission implements IAppPermission { public enum TerminalPermission implements IAppPermission {
CASHIER("Access cashier terminal functions"), CLIENT( CASHIER("Access cashier terminal functions"), CUSTOMER(
"Access client terminal functions"), SELFHELP("Self help terminal"); "Access client terminal functions"), SELFHELP("Self help terminal");
public static final String S_TERMINAL = "TERMINAL"; public static final String S_TERMINAL = "TERMINAL";
public static final String S_CASHIER_TERMINAL = "TERMINAL/CASHIER"; public static final String S_CASHIER_TERMINAL = "TERMINAL/CASHIER";
public static final String S_CLIENT_TERMINAL = "TERMINAL/CLIENT"; public static final String S_CUSTOMER_TERMINAL = "TERMINAL/CUSTOMER";
public static final String S_SELFHELP_TERMINAL = "TERMINAL/SELFHELP"; public static final String S_SELFHELP_TERMINAL = "TERMINAL/SELFHELP";
private final String description; private final String description;
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!