completed implementation for Feature #5143: smartgears service authentication portlet

git-svn-id: http://svn.research-infrastructures.eu/public/d4science/gcube/trunk/portlets/user/my-vres@141536 82a268e6-3cf1-43bd-a215-b396298e98cf
This commit is contained in:
Massimiliano Assante 2017-01-12 17:48:06 +00:00
parent 2e7e33d4be
commit dd69469e4e
9 changed files with 309 additions and 79 deletions

View File

@ -0,0 +1,32 @@
package org.gcube.portlet.user.my_vres.client;
public class GetParameters {
String redirectURI;
String state;
String context;
public GetParameters(String redirectURI, String state, String context) {
super();
this.redirectURI = redirectURI;
this.state = state;
this.context = context;
}
public String getRedirectURI() {
return redirectURI;
}
public String getState() {
return state;
}
public String getContext() {
return context;
}
@Override
public String toString() {
return "GetParameters [redirectURI=" + redirectURI + ", state=" + state + ", context=" + context + "]";
}
}

View File

@ -1,6 +1,13 @@
package org.gcube.portlet.user.my_vres.client; package org.gcube.portlet.user.my_vres.client;
import org.gcube.portlet.user.my_vres.shared.AuthorizationBean;
import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.Location;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.RootPanel;
/** /**
@ -9,12 +16,64 @@ import com.google.gwt.user.client.ui.RootPanel;
* *
*/ */
public class MyVREs implements EntryPoint { public class MyVREs implements EntryPoint {
public static final String GET_REDIRECTURI_PARAMETER = "redirect_uri";
public static final String GET_STATE_PARAMETER = "state";
public static final String GET_CONTEXT_PARAMETER = "context";
public static final String GET_AUTH_TOKEN_PARAMETER = "gcube-token";
private final MyVREsServiceAsync myVREsService = GWT.create(MyVREsService.class);
/**
* This is the entry point method.
*/
public void onModuleLoad() { public void onModuleLoad() {
RootPanel.get("myVREsDIV").add(new VresPanel()); //if no redirectUri is present acts normally
if (Window.Location.getParameter(GET_REDIRECTURI_PARAMETER) == null)
RootPanel.get("myVREsDIV").add(new VresPanel(null));
else {
handleAuthorisation();
}
} }
/**
* if the context is present proceed with the call and redirect otherwise display the VREs for scope selection
*/
private void handleAuthorisation() {
final GetParameters params = getParameters();
if (! params.redirectURI.startsWith("https")) {
RootPanel.get("myVREsDIV").add(new HTML("ERROR: the redirectURI parameter must use HTTP Secure protocol (https)"));
return;
}
if (params.context == null || params.context.compareTo("") == 0) {
RootPanel.get("myVREsDIV").add(new VresPanel(params));
}
else {
myVREsService.getUserToken(params.context, params.state, new AsyncCallback<AuthorizationBean>() {
@Override
public void onSuccess(AuthorizationBean result) {
if (result.isSuccess()) {
Location.assign(params.redirectURI+"?"+GET_AUTH_TOKEN_PARAMETER+"="+result.getToken()+"&"+GET_STATE_PARAMETER+"="+result.getState());
} else {
RootPanel.get("myVREsDIV").add(new HTML("There were issues in managing this request: " + result.getErrorDescription()));
}
}
@Override
public void onFailure(Throwable caught) {
RootPanel.get("myVREsDIV").add(new HTML("An error occurred in the server: " + caught.getMessage()));
}
});
}
}
/**
* check if it has to show just one feed
* @return
*/
private GetParameters getParameters() {
String redirectURI = Window.Location.getParameter(GET_REDIRECTURI_PARAMETER);
String state = Window.Location.getParameter(GET_STATE_PARAMETER);
String context = Window.Location.getParameter(GET_CONTEXT_PARAMETER);
return new GetParameters(redirectURI, state, context);
}
} }

View File

@ -3,6 +3,7 @@ package org.gcube.portlet.user.my_vres.client;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import org.gcube.portlet.user.my_vres.shared.AuthorizationBean;
import org.gcube.portlet.user.my_vres.shared.VRE; import org.gcube.portlet.user.my_vres.shared.VRE;
import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteService;
@ -15,6 +16,7 @@ import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
public interface MyVREsService extends RemoteService { public interface MyVREsService extends RemoteService {
LinkedHashMap<String, ArrayList<VRE>> getUserVREs(); LinkedHashMap<String, ArrayList<VRE>> getUserVREs();
String getSiteLandingPagePath(); String getSiteLandingPagePath();
AuthorizationBean getUserToken(String context, String state);
} }

View File

@ -3,6 +3,7 @@ package org.gcube.portlet.user.my_vres.client;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import org.gcube.portlet.user.my_vres.shared.AuthorizationBean;
import org.gcube.portlet.user.my_vres.shared.VRE; import org.gcube.portlet.user.my_vres.shared.VRE;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
@ -14,4 +15,6 @@ public interface MyVREsServiceAsync {
void getSiteLandingPagePath(AsyncCallback<String> callback); void getSiteLandingPagePath(AsyncCallback<String> callback);
void getUserToken(String context, String state, AsyncCallback<AuthorizationBean> callback);
} }

View File

@ -34,11 +34,9 @@ public class VresPanel extends Composite {
private Image loadingImage = new Image(loading); private Image loadingImage = new Image(loading);
private LinkedHashMap<String, ArrayList<VRE>> cachedVREs = null; private LinkedHashMap<String, ArrayList<VRE>> cachedVREs = null;
boolean hasVres = false; boolean hasVres = false;
public VresPanel() { public VresPanel(GetParameters params) {
super(); super();
loadingImage.getElement().getStyle().setMarginTop(59, Unit.PX); loadingImage.getElement().getStyle().setMarginTop(59, Unit.PX);
mainPanel.setHorizontalAlignment(HasAlignment.ALIGN_CENTER); mainPanel.setHorizontalAlignment(HasAlignment.ALIGN_CENTER);
@ -48,11 +46,12 @@ public class VresPanel extends Composite {
flowPanel.setWidth("100%"); flowPanel.setWidth("100%");
flowPanel.setStyleName("flowPanel"); flowPanel.setStyleName("flowPanel");
catPanel.setWidth("95%"); catPanel.setWidth("95%");
loadVREs(); loadVREs(params);
initWidget(mainPanel); initWidget(mainPanel);
} }
private void loadVREs() { private void loadVREs(final GetParameters params) {
mainPanel.add(loadingImage); mainPanel.add(loadingImage);
myVREsService.getUserVREs(new AsyncCallback<LinkedHashMap<String,ArrayList<VRE>>>() { myVREsService.getUserVREs(new AsyncCallback<LinkedHashMap<String,ArrayList<VRE>>>() {
@ -64,7 +63,7 @@ public class VresPanel extends Composite {
@Override @Override
public void onSuccess(LinkedHashMap<String, ArrayList<VRE>> result) { public void onSuccess(LinkedHashMap<String, ArrayList<VRE>> result) {
cachedVREs = result; cachedVREs = result;
showIconView(); showIconView(params);
} }
}); });
@ -72,7 +71,7 @@ public class VresPanel extends Composite {
} }
private void showIconView() { private void showIconView(final GetParameters params) {
boolean hasVREs = false; boolean hasVREs = false;
mainPanel.clear(); mainPanel.clear();
mainPanel.setHorizontalAlignment(HasAlignment.ALIGN_LEFT); mainPanel.setHorizontalAlignment(HasAlignment.ALIGN_LEFT);
@ -91,7 +90,7 @@ public class VresPanel extends Composite {
for (VRE vre: cachedVREs.get(cat)) { for (VRE vre: cachedVREs.get(cat)) {
if (vre.getName().compareTo("")!= 0) if (vre.getName().compareTo("")!= 0)
hasVREs = true; hasVREs = true;
ClickableVRE vreButton = new ClickableVRE(vre, (i < LOAD_MAX_IMAGE_NO)); ClickableVRE vreButton = new ClickableVRE(myVREsService, vre, (i < LOAD_MAX_IMAGE_NO), params);
flowPanel.add(vreButton); flowPanel.add(vreButton);
i++; i++;
} }

View File

@ -1,5 +1,9 @@
package org.gcube.portlet.user.my_vres.client.widgets; package org.gcube.portlet.user.my_vres.client.widgets;
import org.gcube.portlet.user.my_vres.client.GetParameters;
import org.gcube.portlet.user.my_vres.client.MyVREs;
import org.gcube.portlet.user.my_vres.client.MyVREsServiceAsync;
import org.gcube.portlet.user.my_vres.shared.AuthorizationBean;
import org.gcube.portlet.user.my_vres.shared.VRE; import org.gcube.portlet.user.my_vres.shared.VRE;
import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT;
@ -9,7 +13,9 @@ import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler; import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window.Location; import com.google.gwt.user.client.Window.Location;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.RootPanel;
/** /**
* *
@ -30,11 +36,7 @@ public class ClickableVRE extends HTML {
private String html = ""; private String html = "";
public ClickableVRE() { public ClickableVRE(final MyVREsServiceAsync myVREsService, final VRE vre, final boolean showImage, final GetParameters params) {
super();
}
public ClickableVRE(final VRE vre, final boolean showImage) {
super.setPixelSize(WIDTH, HEIGHT); super.setPixelSize(WIDTH, HEIGHT);
setPixelSize(WIDTH, HEIGHT); setPixelSize(WIDTH, HEIGHT);
if (vre.getName() == null || vre.getName().compareTo("") == 0) { if (vre.getName() == null || vre.getName().compareTo("") == 0) {
@ -56,7 +58,30 @@ public class ClickableVRE extends HTML {
setHTML(html); setHTML(html);
setStyleName("vreButton"); setStyleName("vreButton");
if (params != null) {
addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
myVREsService.getUserToken(vre.getContext(), params.getState(), new AsyncCallback<AuthorizationBean>() {
@Override
public void onSuccess(AuthorizationBean result) {
if (result.isSuccess()) {
Location.assign(params.getRedirectURI()+"?"+MyVREs.GET_AUTH_TOKEN_PARAMETER+"="+result.getToken()+"&"+MyVREs.GET_STATE_PARAMETER+"="+result.getState());
} else {
RootPanel.get("myVREsDIV").add(new HTML("There were issues in managing this request: " + result.getErrorDescription()));
}
}
@Override
public void onFailure(Throwable caught) {
RootPanel.get("myVREsDIV").add(new HTML("An error occurred in the server: " + caught.getMessage()));
}
});
}
});
}
else {
addClickHandler(new ClickHandler() { addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) { public void onClick(ClickEvent event) {
String html = "<div style=\"display: table; text-align:center; width: 100%; height: 75px;\">" + String html = "<div style=\"display: table; text-align:center; width: 100%; height: 75px;\">" +
@ -72,6 +97,7 @@ public class ClickableVRE extends HTML {
timer.schedule(50); timer.schedule(50);
} }
}); });
}
addMouseOverHandler(new MouseOverHandler() { addMouseOverHandler(new MouseOverHandler() {
@Override @Override

View File

@ -10,9 +10,13 @@ import javax.servlet.http.HttpServletRequest;
import org.gcube.common.portal.GCubePortalConstants; import org.gcube.common.portal.GCubePortalConstants;
import org.gcube.common.portal.PortalContext; import org.gcube.common.portal.PortalContext;
import org.gcube.portlet.user.my_vres.client.MyVREsService; import org.gcube.portlet.user.my_vres.client.MyVREsService;
import org.gcube.portlet.user.my_vres.shared.AuthorizationBean;
import org.gcube.portlet.user.my_vres.shared.UserBelonging; import org.gcube.portlet.user.my_vres.shared.UserBelonging;
import org.gcube.portlet.user.my_vres.shared.VRE; import org.gcube.portlet.user.my_vres.shared.VRE;
import org.gcube.vomanagement.usermanagement.GroupManager; import org.gcube.vomanagement.usermanagement.GroupManager;
import org.gcube.vomanagement.usermanagement.exception.GroupRetrievalFault;
import org.gcube.vomanagement.usermanagement.exception.UserManagementSystemException;
import org.gcube.vomanagement.usermanagement.exception.UserRetrievalFault;
import org.gcube.vomanagement.usermanagement.impl.LiferayGroupManager; import org.gcube.vomanagement.usermanagement.impl.LiferayGroupManager;
import org.gcube.vomanagement.usermanagement.impl.LiferayUserManager; import org.gcube.vomanagement.usermanagement.impl.LiferayUserManager;
import org.gcube.vomanagement.usermanagement.model.GCubeGroup; import org.gcube.vomanagement.usermanagement.model.GCubeGroup;
@ -100,7 +104,7 @@ public class MyVREsServiceImpl extends RemoteServiceServlet implements MyVREsSer
for (GCubeGroup vre : vOrg.getChildren()) { for (GCubeGroup vre : vOrg.getChildren()) {
VRE vreToAdd = new VRE(); VRE vreToAdd = new VRE();
vreToAdd.setName(vre.getGroupName()); vreToAdd.setName(vre.getGroupName());
vreToAdd.setGroupName(gm.getInfrastructureScope(vre.getGroupId())); vreToAdd.setContext(gm.getInfrastructureScope(vre.getGroupId()));
long logoId = vre.getLogoId(); long logoId = vre.getLogoId();
String logoURL = gm.getGroupLogoURL(logoId); String logoURL = gm.getGroupLogoURL(logoId);
vreToAdd.setImageURL(logoURL); vreToAdd.setImageURL(logoURL);
@ -167,7 +171,7 @@ public class MyVREsServiceImpl extends RemoteServiceServlet implements MyVREsSer
// //
VRE cool_EM_VRE = new VRE(); VRE cool_EM_VRE = new VRE();
cool_EM_VRE.setName("BiodiversityResearchEnvironment"); cool_EM_VRE.setName("BiodiversityResearchEnvironment");
cool_EM_VRE.setGroupName("/d4science.research-infrastructures.eu/EM/COOLEMVRE"); cool_EM_VRE.setContext("/d4science.research-infrastructures.eu/EM/COOLEMVRE");
cool_EM_VRE.setDescription("cool_EM_VRE VRE Description<br />"+ cool_EM_VRE.setDescription("cool_EM_VRE VRE Description<br />"+
"This Virtual Research Environment is for cool authors, managers and researchers who produce reports containing cool data."); "This Virtual Research Environment is for cool authors, managers and researchers who produce reports containing cool data.");
cool_EM_VRE.setImageURL("https://placehold.it/150x150"); cool_EM_VRE.setImageURL("https://placehold.it/150x150");
@ -176,7 +180,7 @@ public class MyVREsServiceImpl extends RemoteServiceServlet implements MyVREsSer
VRE cool_EM_VRE2 = new VRE(); VRE cool_EM_VRE2 = new VRE();
cool_EM_VRE2.setName("COOL VRE 2"); cool_EM_VRE2.setName("COOL VRE 2");
cool_EM_VRE2.setGroupName("/d4science.research-infrastructures.eu/EM/COOLEMVRE2"); cool_EM_VRE2.setContext("/d4science.research-infrastructures.eu/EM/COOLEMVRE2");
cool_EM_VRE2.setDescription("Cool VRE Description<br />"+ cool_EM_VRE2.setDescription("Cool VRE Description<br />"+
"This Virtual Research Environment is for cool authors, managers and researchers who produce reports containing cool data."); "This Virtual Research Environment is for cool authors, managers and researchers who produce reports containing cool data.");
@ -185,7 +189,7 @@ public class MyVREsServiceImpl extends RemoteServiceServlet implements MyVREsSer
VRE cool_EM_VRE3 = new VRE(); VRE cool_EM_VRE3 = new VRE();
cool_EM_VRE3.setName("COOL EM VRE TRE"); cool_EM_VRE3.setName("COOL EM VRE TRE");
cool_EM_VRE3.setGroupName("/d4science.research-infrastructures.eu/EM/COOlVRE3"); cool_EM_VRE3.setContext("/d4science.research-infrastructures.eu/EM/COOlVRE3");
cool_EM_VRE3.setDescription("Cool VRE Description<br />"+ cool_EM_VRE3.setDescription("Cool VRE Description<br />"+
"This Virtual Research Environment is for cool authors, managers and researchers who produce reports containing cool data."); "This Virtual Research Environment is for cool authors, managers and researchers who produce reports containing cool data.");
@ -203,7 +207,7 @@ public class MyVREsServiceImpl extends RemoteServiceServlet implements MyVREsSer
VRE demo = new VRE(); VRE demo = new VRE();
demo.setName("Demo"); demo.setName("Demo");
demo.setGroupName("/d4science.research-infrastructures.eu/EM/Demo"); demo.setContext("/d4science.research-infrastructures.eu/EM/Demo");
demo.setDescription("Cool VRE Description<br />"+ demo.setDescription("Cool VRE Description<br />"+
"This Virtual Research Environment is for cool authors, managers and researchers who produce reports containing cool data."); "This Virtual Research Environment is for cool authors, managers and researchers who produce reports containing cool data.");
@ -212,7 +216,7 @@ public class MyVREsServiceImpl extends RemoteServiceServlet implements MyVREsSer
VRE vreGCM = new VRE(); VRE vreGCM = new VRE();
vreGCM.setName("GCM"); vreGCM.setName("GCM");
vreGCM.setGroupName("/d4science.research-infrastructures.eu/EM/GCM"); vreGCM.setContext("/d4science.research-infrastructures.eu/EM/GCM");
vreGCM.setDescription("Global Ocean Chlorophyll Monitoring (GCM) Virtual Research Environment<br />" vreGCM.setDescription("Global Ocean Chlorophyll Monitoring (GCM) Virtual Research Environment<br />"
+ "The phytoplankton plays a similar role to terrestrial green plants in the photosynthetic process and are credited with removing as much carbon dioxide from the atmosphere as their earthbound counterparts, making it important to monitor and model plankton into calculations of future climate change."); + "The phytoplankton plays a similar role to terrestrial green plants in the photosynthetic process and are credited with removing as much carbon dioxide from the atmosphere as their earthbound counterparts, making it important to monitor and model plankton into calculations of future climate change.");
vreGCM.setImageURL("https://placehold.it/150x150"); vreGCM.setImageURL("https://placehold.it/150x150");
@ -242,5 +246,47 @@ public class MyVREsServiceImpl extends RemoteServiceServlet implements MyVREsSer
return toReturn; return toReturn;
} }
@Override
public AuthorizationBean getUserToken( String context, String state) {
if (state == null || state.compareTo("")== 0) {
return new AuthorizationBean(null, null, false, "State is null, please use a unique string value of your choice that is hard to guess (e.g. state=7d12bf13-111c-4f46-ab06-9e9e08ad377b). Used to prevent CSRF attacks");
}
if (context == null || context.compareTo("")== 0) {
return new AuthorizationBean(null, null, false, "Infrastructure Context is null");
}
PortalContext pContext = PortalContext.getConfiguration();
GCubeUser currentUser = pContext.getCurrentUser(getThreadLocalRequest());
String username = currentUser.getUsername();
long userId = currentUser.getUserId();
_log.debug("Creating AuthorizationBean for user " + username);
if (isWithinPortal()) {
GroupManager gm = new LiferayGroupManager();
try {
long groupId = gm.getGroupIdFromInfrastructureScope(context);
_log.debug("Verifying user permission for scope " + context);
if (! gm.listGroupsByUser(userId).contains(gm.getGroup(groupId))) {
return new AuthorizationBean(null, null, false, "User having username: " + username + " is not authorised in context: " + context);
}
} catch (IllegalArgumentException | UserManagementSystemException | GroupRetrievalFault e) {
_log.error("Something wrong in the Context parameter: " + e.getMessage());
return new AuthorizationBean(null, null, false, "Something wrong in the Context parameter: " + e.getMessage());
} catch (UserRetrievalFault e) {
return new AuthorizationBean(null, null, false, "Something wrong in the user retrieval " + e.getMessage());
}
}
if (username == null) {
_log.error("Something wrong in retrieving the user");
return new AuthorizationBean(null, null, false, "Something wrong in retrieving the logged in user, is session expired?");
}
String token = pContext.getCurrentUserToken(context, username);
if (token == null) {
_log.error("Something wrong in retrieving the user token in this context: " + context + " username="+username);
return new AuthorizationBean(null, null, false, "Something wrong in retrieving the user token in this context: " + context + " username="+username);
}
_log.debug("Authorisation OAUTH returning user token in this context: " + context + " username="+username);
return new AuthorizationBean(token, state, true, null);
}
} }

View File

@ -0,0 +1,63 @@
package org.gcube.portlet.user.my_vres.shared;
import java.io.Serializable;
@SuppressWarnings("serial")
public class AuthorizationBean implements Serializable {
private String token;
private String state;
private boolean success;
private String errorDescription;
public AuthorizationBean() {
super();
// TODO Auto-generated constructor stub
}
public AuthorizationBean(String token, String state, boolean success, String errorDescription) {
super();
this.token = token;
this.state = state;
this.success = success;
this.errorDescription = errorDescription;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getErrorDescription() {
return errorDescription;
}
public void setErrorDescription(String errorDescription) {
this.errorDescription = errorDescription;
}
@Override
public String toString() {
return "AuthorizationBean [token=" + token + ", state=" + state + ", success=" + success + ", errorDescription="
+ errorDescription + "]";
}
}

View File

@ -2,10 +2,7 @@ package org.gcube.portlet.user.my_vres.shared;
import java.io.Serializable; import java.io.Serializable;
/** /**
*
* @author Massimiliano Assante ISTI-CNR * @author Massimiliano Assante ISTI-CNR
*
* @version 2.0 Jan 10th 2012
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class ResearchEnvironment implements Serializable{ public class ResearchEnvironment implements Serializable{
@ -14,8 +11,8 @@ public class ResearchEnvironment implements Serializable{
private String description; private String description;
private String imageURL; private String imageURL;
//the infrastructure scope
private String groupName; private String context;
private String friendlyURL; private String friendlyURL;
@ -26,13 +23,13 @@ public class ResearchEnvironment implements Serializable{
} }
public ResearchEnvironment(String name, String description, public ResearchEnvironment(String name, String description,
String imageURL, String groupName, String friendlyURL, String imageURL, String context, String friendlyURL,
UserBelonging userBelonging) { UserBelonging userBelonging) {
super(); super();
this.name = name; this.name = name;
this.description = description; this.description = description;
this.imageURL = imageURL; this.imageURL = imageURL;
this.groupName = groupName; this.context = context;
this.friendlyURL = friendlyURL; this.friendlyURL = friendlyURL;
this.userBelonging = userBelonging; this.userBelonging = userBelonging;
} }
@ -60,13 +57,16 @@ public class ResearchEnvironment implements Serializable{
public void setImageURL(String imageURL) { public void setImageURL(String imageURL) {
this.imageURL = imageURL; this.imageURL = imageURL;
} }
/**
public String getGroupName() { *
return groupName; * @return the infrastructure scope
*/
public String getContext() {
return context;
} }
public void setGroupName(String groupName) { public void setContext(String context) {
this.groupName = groupName; this.context = context;
} }
public String getFriendlyURL() { public String getFriendlyURL() {