NIHVIVO-1386 Self-editors don't get SiteAdmin link in top nav. They also don't get revision info link in footer. Implemented with new User template model class. Changed tests for loginName, showFlag1Status in templates to use this object.

This commit is contained in:
rjy7 2010-11-23 01:28:39 +00:00
parent 130b641081
commit ca6ab8288e
18 changed files with 225 additions and 77 deletions

View file

@ -40,7 +40,7 @@ public class PrimitiveRdfEdit extends FreemarkerHttpServlet{
} }
@Override @Override
protected int requiresLoginLevel() { protected int requiredLoginLevel() {
return LoginStatusBean.EDITOR; return LoginStatusBean.EDITOR;
} }

View file

@ -43,6 +43,8 @@ public class FreemarkerComponentGenerator extends FreemarkerHttpServlet {
request.setAttribute("ftl_search", get("search", root, config, vreq)); request.setAttribute("ftl_search", get("search", root, config, vreq));
request.setAttribute("ftl_footer", get("footer", root, config, vreq)); request.setAttribute("ftl_footer", get("footer", root, config, vreq));
request.setAttribute("ftl_googleAnalytics", get("googleAnalytics", root, config, vreq)); request.setAttribute("ftl_googleAnalytics", get("googleAnalytics", root, config, vreq));
request.setAttribute("freemarkerComponentsDone", true);
} }
private String get(String templateName, Map<String, Object> root, Configuration config, HttpServletRequest request) { private String get(String templateName, Map<String, Object> root, Configuration config, HttpServletRequest request) {

View file

@ -32,6 +32,7 @@ import edu.cornell.mannlib.vitro.webapp.utils.StringUtils;
import edu.cornell.mannlib.vitro.webapp.web.BreadCrumbsUtil; import edu.cornell.mannlib.vitro.webapp.web.BreadCrumbsUtil;
import edu.cornell.mannlib.vitro.webapp.web.ContentType; import edu.cornell.mannlib.vitro.webapp.web.ContentType;
import edu.cornell.mannlib.vitro.webapp.web.PortalWebUtil; import edu.cornell.mannlib.vitro.webapp.web.PortalWebUtil;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.User;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.files.Scripts; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.files.Scripts;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.files.Stylesheets; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.files.Stylesheets;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.menu.TabMenu; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.menu.TabMenu;
@ -45,7 +46,7 @@ public class FreemarkerHttpServlet extends VitroHttpServlet {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(FreemarkerHttpServlet.class); private static final Log log = LogFactory.getLog(FreemarkerHttpServlet.class);
private static final int FILTER_SECURITY_LEVEL = LoginStatusBean.EDITOR;
public static final String PAGE_TEMPLATE_TYPE = "page"; public static final String PAGE_TEMPLATE_TYPE = "page";
public static final String BODY_TEMPLATE_TYPE = "body"; public static final String BODY_TEMPLATE_TYPE = "body";
@ -108,7 +109,7 @@ public class FreemarkerHttpServlet extends VitroHttpServlet {
} }
private boolean requiredLoginLevelNotFound(HttpServletRequest request, HttpServletResponse response) { private boolean requiredLoginLevelNotFound(HttpServletRequest request, HttpServletResponse response) {
int requiredLoginLevel = requiresLoginLevel(); int requiredLoginLevel = requiredLoginLevel();
// checkLoginStatus() does a redirect if the user is not logged in. // checkLoginStatus() does a redirect if the user is not logged in.
if (requiredLoginLevel > LoginStatusBean.ANYBODY && !checkLoginStatus(request, response, requiredLoginLevel)) { if (requiredLoginLevel > LoginStatusBean.ANYBODY && !checkLoginStatus(request, response, requiredLoginLevel)) {
return true; return true;
@ -116,11 +117,7 @@ public class FreemarkerHttpServlet extends VitroHttpServlet {
return false; return false;
} }
protected boolean requiresLogin() { protected int requiredLoginLevel() {
return false;
}
protected int requiresLoginLevel() {
// By default, user does not need to be logged in to view pages. // By default, user does not need to be logged in to view pages.
// Subclasses that require login to process their page will override to return the required login level. // Subclasses that require login to process their page will override to return the required login level.
// NB This method can't be static, because then the superclass method gets called rather than // NB This method can't be static, because then the superclass method gets called rather than
@ -382,7 +379,7 @@ public class FreemarkerHttpServlet extends VitroHttpServlet {
PortalWebUtil.populateSearchOptions(portal, appBean, vreq.getWebappDaoFactory().getPortalDao()); PortalWebUtil.populateSearchOptions(portal, appBean, vreq.getWebappDaoFactory().getPortalDao());
PortalWebUtil.populateNavigationChoices(portal, vreq, appBean, vreq.getWebappDaoFactory().getPortalDao()); PortalWebUtil.populateNavigationChoices(portal, vreq, appBean, vreq.getWebappDaoFactory().getPortalDao());
map.putAll(getLoginValues(vreq)); map.put("user", new User(vreq));
UrlBuilder urlBuilder = new UrlBuilder(portal); UrlBuilder urlBuilder = new UrlBuilder(portal);
map.put("version", getRevisionInfo(urlBuilder)); map.put("version", getRevisionInfo(urlBuilder));
@ -409,30 +406,11 @@ public class FreemarkerHttpServlet extends VitroHttpServlet {
return map; return map;
} }
private TabMenu getTabMenu(VitroRequest vreq) { private TabMenu getTabMenu(VitroRequest vreq) {
int portalId = vreq.getPortal().getPortalId(); int portalId = vreq.getPortal().getPortalId();
return new TabMenu(vreq, portalId); return new TabMenu(vreq, portalId);
} }
private final Map<String, Object> getLoginValues(VitroRequest vreq) {
Map<String, Object> map = new HashMap<String, Object>();
LoginStatusBean loginBean = LoginStatusBean.getBean(vreq);
if (loginBean.isLoggedIn()) {
map.put("loginName", loginBean.getUsername());
if (loginBean.isLoggedInAtLeast(FILTER_SECURITY_LEVEL)) {
ApplicationBean appBean = vreq.getAppBean();
if (appBean.isFlag1Active()) {
map.put("showFlag1SearchField", true);
}
}
}
return map;
}
private final Map<String, Object> getCopyrightInfo(Portal portal) { private final Map<String, Object> getCopyrightInfo(Portal portal) {
Map<String, Object> copyright = null; Map<String, Object> copyright = null;

View file

@ -52,7 +52,7 @@ import edu.cornell.mannlib.vitro.webapp.utils.NamespaceMapper;
import edu.cornell.mannlib.vitro.webapp.utils.NamespaceMapperFactory; import edu.cornell.mannlib.vitro.webapp.utils.NamespaceMapperFactory;
import edu.cornell.mannlib.vitro.webapp.web.ContentType; import edu.cornell.mannlib.vitro.webapp.web.ContentType;
import edu.cornell.mannlib.vitro.webapp.web.jsptags.StringProcessorTag; import edu.cornell.mannlib.vitro.webapp.web.jsptags.StringProcessorTag;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.IndividualTemplateModel; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.IndividualTemplateModel;
/** /**
* Handles requests for entity information. * Handles requests for entity information.

View file

@ -16,7 +16,7 @@ import edu.cornell.mannlib.vitro.webapp.beans.VClassGroup;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet.ResponseValues; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.utils.StringUtils; import edu.cornell.mannlib.vitro.webapp.utils.StringUtils;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.IndividualTemplateModel; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.IndividualTemplateModel;
import freemarker.template.Configuration; import freemarker.template.Configuration;
/** /**

View file

@ -19,10 +19,20 @@ public class RevisionInfoController extends FreemarkerHttpServlet {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(RevisionInfoController.class); private static final Log log = LogFactory.getLog(RevisionInfoController.class);
private static final String TEMPLATE_DEFAULT = "revisionInfo.ftl"; private static final String TEMPLATE_DEFAULT = "revisionInfo.ftl";
private static final int REQUIRED_LOGIN_LEVEL = LoginStatusBean.EDITOR;
/* requiredLoginLevel() must be an instance method, else, due to the way sublcass
* hiding works, when called from FreemarkerHttpServlet we will get its own method,
* rather than the subclass method. To figure out whether to display links at the
* page level, we need another, static method.
*/
public static int staticRequiredLoginLevel() {
return REQUIRED_LOGIN_LEVEL;
}
@Override @Override
protected int requiresLoginLevel() { protected int requiredLoginLevel() {
return LoginStatusBean.EDITOR; return REQUIRED_LOGIN_LEVEL;
} }
@Override @Override

View file

@ -28,16 +28,25 @@ public class SiteAdminController extends FreemarkerHttpServlet {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(SiteAdminController.class); private static final Log log = LogFactory.getLog(SiteAdminController.class);
private static final String TEMPLATE_DEFAULT = "siteAdmin-main.ftl"; private static final String TEMPLATE_DEFAULT = "siteAdmin-main.ftl";
private static final int REQUIRED_LOGIN_LEVEL = LoginStatusBean.EDITOR;
@Override @Override
public String getTitle(String siteName) { public String getTitle(String siteName) {
return siteName + " Site Administration"; return siteName + " Site Administration";
} }
/* requiredLoginLevel() must be an instance method, else, due to the way sublcass
* hiding works, when called from FreemarkerHttpServlet we will get its own method,
* rather than the subclass method. To figure out whether to display links at the
* page level, we need another, static method.
*/
public static int staticRequiredLoginLevel() {
return REQUIRED_LOGIN_LEVEL;
}
@Override @Override
protected int requiresLoginLevel() { protected int requiredLoginLevel() {
// User must be logged in to view this page. return REQUIRED_LOGIN_LEVEL;
return LoginStatusBean.EDITOR;
} }
@Override @Override

View file

@ -38,6 +38,7 @@ public class TestController extends FreemarkerHttpServlet {
body.put("title", "Freemarker Test"); body.put("title", "Freemarker Test");
return new TemplateResponseValues(TEMPLATE_DEFAULT, body); return new TemplateResponseValues(TEMPLATE_DEFAULT, body);
} }
@Override @Override

View file

@ -72,8 +72,8 @@ import edu.cornell.mannlib.vitro.webapp.search.lucene.SimpleLuceneHighlighter;
import edu.cornell.mannlib.vitro.webapp.utils.FlagMathUtils; import edu.cornell.mannlib.vitro.webapp.utils.FlagMathUtils;
import edu.cornell.mannlib.vitro.webapp.utils.Html2Text; import edu.cornell.mannlib.vitro.webapp.utils.Html2Text;
import edu.cornell.mannlib.vitro.webapp.utils.StringUtils; import edu.cornell.mannlib.vitro.webapp.utils.StringUtils;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.IndividualTemplateModel;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.LinkTemplateModel; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.LinkTemplateModel;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.IndividualTemplateModel;
/** /**
* PagedSearchController is the new search controller that interacts * PagedSearchController is the new search controller that interacts

View file

@ -0,0 +1,71 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.web.templatemodels;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vitro.webapp.beans.ApplicationBean;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.RevisionInfoController;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.SiteAdminController;
public class User extends BaseTemplateModel {
private static final Log log = LogFactory.getLog(User.class);
private enum Access {
SITE_ADMIN(SiteAdminController.staticRequiredLoginLevel()),
REVISION_INFO(RevisionInfoController.staticRequiredLoginLevel()),
FILTER_SECURITY(LoginStatusBean.EDITOR);
private final int requiredLoginLevel;
Access(int requiredLoginLevel) {
this.requiredLoginLevel = requiredLoginLevel;
}
int requiredLoginLevel() {
return this.requiredLoginLevel;
}
}
private LoginStatusBean loginBean = null;
private VitroRequest vreq = null;
public User(VitroRequest vreq) {
this.vreq = vreq;
loginBean = LoginStatusBean.getBean(vreq);
}
public boolean isLoggedIn() {
return loginBean.isLoggedIn();
}
public String getLoginName() {
return loginBean.getUsername();
}
public boolean getHasSiteAdminAccess() {
return loginBean.isLoggedInAtLeast(Access.SITE_ADMIN.requiredLoginLevel());
}
public boolean getHasRevisionInfoAccess() {
return loginBean.isLoggedInAtLeast(Access.REVISION_INFO.requiredLoginLevel());
}
public boolean getShowFlag1SearchField() {
boolean showFlag1SearchField = false;
if (loginBean.isLoggedInAtLeast(Access.FILTER_SECURITY.requiredLoginLevel)) {
ApplicationBean appBean = vreq.getAppBean();
if (appBean.isFlag1Active()) {
showFlag1SearchField = true;
}
}
return showFlag1SearchField;
}
}

View file

@ -1,6 +1,6 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */ /* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.web.templatemodels; package edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -19,6 +19,7 @@ import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.Route;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
import edu.cornell.mannlib.vitro.webapp.web.ViewFinder; import edu.cornell.mannlib.vitro.webapp.web.ViewFinder;
import edu.cornell.mannlib.vitro.webapp.web.ViewFinder.ClassView; import edu.cornell.mannlib.vitro.webapp.web.ViewFinder.ClassView;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.BaseTemplateModel;
public class IndividualTemplateModel extends BaseTemplateModel { public class IndividualTemplateModel extends BaseTemplateModel {
@ -30,13 +31,6 @@ public class IndividualTemplateModel extends BaseTemplateModel {
protected VitroRequest vreq; protected VitroRequest vreq;
protected UrlBuilder urlBuilder; protected UrlBuilder urlBuilder;
// private PropertyListTemplateModel propertyList;
// RY The IndividualTemplateModel object needs access to the request object.
// The only other template model that does is MainMenu. We could provide an
// interface for RequestAware template models, but they still wouldn't share any code.
// If they both derive from a common RequestAwareTemplateModel class, we might be
// locking ourselves in too tightly to that class hierarchy.
public IndividualTemplateModel(Individual individual, VitroRequest vreq) { public IndividualTemplateModel(Individual individual, VitroRequest vreq) {
this.individual = individual; this.individual = individual;
this.vreq = vreq; this.vreq = vreq;
@ -150,6 +144,19 @@ public class IndividualTemplateModel extends BaseTemplateModel {
return links; return links;
} }
public static List<IndividualTemplateModel> getIndividualTemplateModelList(List<Individual> individuals, VitroRequest vreq) {
List<IndividualTemplateModel> models = new ArrayList<IndividualTemplateModel>(individuals.size());
for (Individual individual : individuals) {
models.add(new IndividualTemplateModel(individual, vreq));
}
return models;
}
public List<Object> getPropertyList() {
PropertyListBuilder propListBuilder = new PropertyListBuilder(individual, vreq);
return propListBuilder.getPropertyList();
}
/* These methods simply forward to the Individual methods. It would be desirable to implement a scheme /* These methods simply forward to the Individual methods. It would be desirable to implement a scheme
for proxying or delegation so that the methods don't need to be simply listed here. for proxying or delegation so that the methods don't need to be simply listed here.
A Ruby-style method missing method would be ideal. A Ruby-style method missing method would be ideal.
@ -189,19 +196,6 @@ public class IndividualTemplateModel extends BaseTemplateModel {
return individual.getLocalName(); return individual.getLocalName();
} }
public static List<IndividualTemplateModel> getIndividualTemplateModelList(List<Individual> individuals, VitroRequest vreq) {
List<IndividualTemplateModel> models = new ArrayList<IndividualTemplateModel>(individuals.size());
for (Individual individual : individuals) {
models.add(new IndividualTemplateModel(individual, vreq));
}
return models;
}
private boolean isExternallyLinkedNamespace(String namespace,List<String> externallyLinkedNamespaces) {
return externallyLinkedNamespaces.contains(namespace);
}
// public Map< > getPropertyList
// public Map< > getPropertyGroupList
} }

View file

@ -0,0 +1,83 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.beans.ObjectProperty;
import edu.cornell.mannlib.vitro.webapp.beans.Property;
import edu.cornell.mannlib.vitro.webapp.beans.PropertyGroup;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.dao.PropertyGroupDao;
import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
import edu.cornell.mannlib.vitro.webapp.filters.VitroRequestPrep;
public class PropertyListBuilder {
private static final Log log = LogFactory.getLog(PropertyListBuilder.class);
private static final int MAX_GROUP_DISPLAY_RANK = 99;
// Don't include these properties in the list.
private static final Collection<String> SUPPRESSED_OBJECT_PROPERTIES = Collections
.unmodifiableCollection(Arrays
.asList(new String[] { VitroVocabulary.IND_MAIN_IMAGE }));
protected Individual subject;
protected VitroRequest vreq;
PropertyListBuilder(Individual individual, VitroRequest vreq) {
this.subject = individual;
this.vreq = vreq;
}
protected List<Object> getPropertyList() {
// Determine whether we're editing or not.
// These tests may change once self-editing issues are straightened out.
boolean isSelfEditing = VitroRequestPrep.isSelfEditing(vreq);
boolean isCurator = LoginStatusBean.getBean(vreq).isLoggedInAtLeast(LoginStatusBean.CURATOR);
boolean isEditing = isSelfEditing || isCurator;
// Determine whether to return a grouped or ungrouped property list.
// If the call specified ungrouped, use ungrouped.
// If the call specified grouped:
WebappDaoFactory wdf = vreq.getWebappDaoFactory();
PropertyGroupDao pgDao = wdf.getPropertyGroupDao();
List <PropertyGroup> groupsList = pgDao.getPublicGroups(false); // may be returned empty but not null
// Use ungrouped if no property groups are defined
// If < 2 property groups are populated, we also want ungrouped, but we won't know that until we
// get the property list.
// Assemble the property list
List<Property> mergedPropertyList = new ArrayList<Property>();
// First get the properties this entity actually has, presumably populated with statements
List<ObjectProperty> objectPropertyList = subject.getObjectPropertyList();
for (ObjectProperty op : objectPropertyList) {
if (!SUPPRESSED_OBJECT_PROPERTIES.contains(op)) {
op.setEditLabel(op.getDomainPublic());
mergedPropertyList.add(op);
}else{
log.debug("suppressed " + op.getURI());
}
}
// If < 2 property groups populated, we want ungrouped.
// This can just be handled by not including a group, or using an "empty" group with no name.
return null;
}
}

View file

@ -2,8 +2,6 @@
<#-- Template for property listing on individual profile page --> <#-- Template for property listing on individual profile page -->
<#if individual.propertyGroups??> <#--
<#assign properties = individual.propertyList>
<#elseif individual.properties??> -->
</#if>

View file

@ -12,11 +12,13 @@
<ul id="otherMenu"> <ul id="otherMenu">
<@l.firstLastList> <@l.firstLastList>
<#if loginName??> <#if user.loggedIn>
<li> <li>
Logged in as <strong>${loginName}</strong> (<a href="${urls.logout}">Log out</a>) Logged in as <strong>${user.loginName}</strong> (<a href="${urls.logout}">Log out</a>)
</li> </li>
<#if user.hasSiteAdminAccess>
<li><a href="${urls.siteAdmin}">Site Admin</a></li> <li><a href="${urls.siteAdmin}">Site Admin</a></li>
</#if>
<#else> <#else>
<li><a title="log in to manage this site" href="${urls.login}">Log in</a></li> <li><a title="log in to manage this site" href="${urls.login}">Log in</a></li>
</#if> </#if>

View file

@ -4,9 +4,9 @@
<form id="searchForm" action="${urls.search}" > <form id="searchForm" action="${urls.search}" >
<label for="search">Search </label> <label for="search">Search </label>
<#if showFlag1SearchField??> <#if user.showFlag1SearchField>
<select id="search-form-modifier" name="flag1" class="form-item" > <select id="search-form-modifier" name="flag1" class="form-item" >
<option value="nofiltering" selected="selected">entire database (${loginName})</option> <option value="nofiltering" selected="selected">entire database (${user.loginName})</option>
<option value="${portalId}">${siteTagline!}</option> <option value="${portalId}">${siteTagline!}</option>
</select> </select>
<#else> <#else>

View file

@ -2,9 +2,9 @@
<#-- Template for version/revision information --> <#-- Template for version/revision information -->
<#-- Only show version info if user is logged in --> <#-- Only show version info if user has access -->
<#if loginName??> <#if user.hasRevisionInfoAccess>
<div id="version"> <div id="revision">
Version <a href="${version.moreInfoUrl}">${version.label}</a> Version <a href="${version.moreInfoUrl}">${version.label}</a>
</div> </div>
</#if> </#if>

View file

@ -3,9 +3,9 @@
<#-- Login widget --> <#-- Login widget -->
<#macro assets> <#macro assets>
<#-- RY This test should be replaced by controller logic which doesn't display any assets if the user is logged in. <#-- RY This test should be replaced by widget controller logic which doesn't display any assets if the user is logged in.
See NIHVIVO-1357. This test does nothing, since loginName has not been put into the data model. See NIHVIVO-1357. This test does nothing, since user has not been put into the data model.
<#if ! loginName?has_content> --> <#if ! user.loggedIn> -->
${stylesheets.add("/css/login.css")} ${stylesheets.add("/css/login.css")}
${scripts.add("/js/jquery.js", "/js/login/loginUtils.js")} ${scripts.add("/js/jquery.js", "/js/login/loginUtils.js")}
<#-- ${headScripts.add("")} --> <#-- ${headScripts.add("")} -->