/**
* Copyright © 2014 Instituto Superior Técnico
*
* This file is part of FenixEdu CMS.
*
* FenixEdu CMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FenixEdu CMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FenixEdu CMS. If not, see <http://www.gnu.org/licenses/>.
*/
package org.fenixedu.cms.ui;
import static java.util.stream.Collectors.toList;
import static org.fenixedu.cms.domain.PermissionEvaluation.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.ZipFile;
import javax.servlet.http.HttpServletResponse;
import org.apache.tika.io.FilenameUtils;
import org.fenixedu.bennu.core.domain.Bennu;
import org.fenixedu.bennu.core.domain.User;
import org.fenixedu.bennu.core.domain.groups.PersistentGroup;
import org.fenixedu.bennu.core.groups.DynamicGroup;
import org.fenixedu.bennu.core.groups.Group;
import org.fenixedu.bennu.core.security.Authenticate;
import org.fenixedu.bennu.portal.domain.MenuFunctionality;
import org.fenixedu.bennu.portal.domain.PortalConfiguration;
import org.fenixedu.bennu.signals.DomainObjectEvent;
import org.fenixedu.bennu.signals.Signal;
import org.fenixedu.bennu.social.domain.api.GoogleAPI;
import org.fenixedu.bennu.social.domain.user.GoogleUser;
import org.fenixedu.bennu.spring.portal.SpringApplication;
import org.fenixedu.bennu.spring.portal.SpringFunctionality;
import org.fenixedu.cms.domain.*;
import org.fenixedu.cms.domain.PermissionsArray.Permission;
import org.fenixedu.cms.exceptions.CmsDomainException;
import org.fenixedu.commons.i18n.LocalizedString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.servlet.view.RedirectView;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.analytics.Analytics;
import com.google.api.services.analytics.model.Account;
import com.google.api.services.analytics.model.Accounts;
import com.google.api.services.analytics.model.Webproperties;
import com.google.api.services.analytics.model.Webproperty;
import com.google.common.base.Strings;
import com.google.common.io.Files;
import pt.ist.fenixframework.Atomic;
import pt.ist.fenixframework.Atomic.TxMode;
import pt.ist.fenixframework.FenixFramework;
@SpringApplication(group = "logged", path = "cms", title = "application.title.cms")
@SpringFunctionality(app = AdminSites.class, title = "application.admin-portal.title")
@RequestMapping("/cms/sites")
public class AdminSites {
private static final String ZIP_MIME_TYPE = "application/zip";
private static final String JSON = "application/json;charset=utf-8";
protected static final Logger LOGGER = LoggerFactory.getLogger(AdminSites.class);
private static final int ITEMS_PER_PAGE = 10;
private static final int ITEMS_PER_FOLDER_HOME = 5;
@RequestMapping
public String list(Model model) {
Map<CMSFolder, List<Site>> sitesByFolders = Bennu.getInstance().getCmsFolderSet().stream()
.collect(Collectors.toMap(Function.identity(), folder -> folder.getSiteSet().stream()
.filter(site -> PermissionEvaluation.canAccess(Authenticate.getUser(), site))
.sorted(Comparator.comparing(Site::getCreationDate).reversed())
.limit(ITEMS_PER_FOLDER_HOME)
.collect(toList()), (u, v) -> {
throw new IllegalStateException(String.format("Duplicate key %s", u));
},
() -> new TreeMap<>(Comparator.comparing(folder -> ((MenuFunctionality) folder.getFunctionality()).getTitle()))));
AtomicLong sitesWithoutFolderCount = new AtomicLong();
List<Site> sitesWithoutFolder = Bennu.getInstance().getSitesSet().stream()
.filter(site -> site.getFolder() == null)
.filter(site -> PermissionEvaluation.canAccess(Authenticate.getUser(), site))
.peek(site -> sitesWithoutFolderCount.incrementAndGet())
.sorted(Comparator.comparing(Site::getCreationDate).reversed())
.limit(ITEMS_PER_FOLDER_HOME).collect(toList());
List<CMSFolder> foldersSorter = sitesByFolders.keySet().stream().sorted((o1, o2) -> {
if (o1 == null) {
return Integer.MIN_VALUE;
}
if (o2 == null) {
return Integer.MAX_VALUE;
}
return o1.getFunctionality().getTitle().getContent().compareTo(o2.getFunctionality().getTitle().getContent());
}).collect(Collectors.toList());
model.addAttribute("defaultSite", Bennu.getInstance().getDefaultSite());
model.addAttribute("foldersSorted",foldersSorter);
model.addAttribute("sitesByFolders", sitesByFolders);
model.addAttribute("sitesWithoutFolder", sitesWithoutFolder);
model.addAttribute("sitesWithoutFolderCount", sitesWithoutFolderCount);
model.addAttribute("roles", Bennu.getInstance().getRoleTemplatesSet().stream().sorted(Comparator.comparing(x -> x.getDescription().getContent())).collect(Collectors.toSet()));
model.addAttribute("folders", Bennu.getInstance().getCmsFolderSet().stream().sorted(Comparator.comparing(x -> x.getFunctionality().getTitle().getContent())).collect(Collectors.toList()));
model.addAttribute("allPermissions", PermissionsArray.all());
model.addAttribute("cmsSettings", CmsSettings.getInstance());
model.addAttribute("isManager", DynamicGroup.get("managers").isMember(
Authenticate.getUser()));
model.addAttribute("templates", Site.getTemplates());
model.addAttribute("themes", Bennu.getInstance().getCMSThemesSet().stream()
.sorted(Comparator.comparing(CMSTheme::getName)).collect(Collectors.toList()));
return "fenixedu-cms/manage";
}
@RequestMapping("/search")
public String search(Model model, @RequestParam(required = false, defaultValue = "1") int page,
@RequestParam(required = false) String query, @RequestParam(required = false) String tag) {
if((tag== null || tag.equals("") )&& query ==null){
return list(model);
}
CMSFolder selectedFolder = null;
if (tag != null && !tag.equals("")){
if (!(tag.equals("\u2718") || tag.equals("untagged"))) {
selectedFolder = Bennu.getInstance().getCmsFolderSet().stream()
.filter(x -> x.getFunctionality().getPath().equals(tag))
.findAny().orElseThrow(CmsDomainException::notFound);
model.addAttribute("folder", selectedFolder);
}
if(selectedFolder == null){
model.addAttribute("folder", null);
}
}
final CMSFolder finalSelectedFolder = selectedFolder;
List<Site> results = Bennu.getInstance().getSitesSet().stream()
.filter(site -> tag==null || site.getFolder() == finalSelectedFolder)
.filter(site -> PermissionEvaluation.canAccess(Authenticate.getUser(), site))
.filter(site -> query==null || SearchUtils.matches(site, query.toLowerCase()))
.sorted(Comparator.comparing(Site::getCreationDate).reversed())
.collect(toList());
List<CMSFolder> folderList = Bennu.getInstance().getCmsFolderSet().stream().filter(folder -> folder.getSiteSet().stream().anyMatch(site ->
PermissionEvaluation.canAccess(Authenticate.getUser(), site))).sorted((o1, o2) -> {
if (o1 == null) {
return Integer.MIN_VALUE;
}
if (o2 == null) {
return Integer.MAX_VALUE;
}
return o1.getFunctionality().getTitle().getContent().compareTo(o2.getFunctionality().getTitle().getContent());
}).collect(Collectors.toList());
if(Bennu.getInstance().getSitesSet().stream().filter(site -> site.getFolder() ==null).anyMatch(site ->
PermissionEvaluation.canAccess(Authenticate.getUser(), site))) {
folderList.add(null);
}
model.addAttribute("defaultSite", Bennu.getInstance().getDefaultSite());
model.addAttribute("foldersSorted", folderList);
SearchUtils.Partition<Site> partition = new SearchUtils.Partition<>(results, Comparator.comparing(Site::getCreationDate).reversed(),ITEMS_PER_PAGE, page);
model.addAttribute("partition", partition);
model.addAttribute("sites", partition.getItems());
model.addAttribute("query",query);
model.addAttribute("folders", Bennu.getInstance().getCmsFolderSet().stream().sorted(Comparator.comparing(x -> x.getFunctionality().getTitle().getContent())).collect(Collectors.toList()));
model.addAttribute("tag", tag);
model.addAttribute("roles", Bennu.getInstance().getRoleTemplatesSet().stream().sorted(Comparator.comparing( x -> x.getDescription().getContent())).collect(Collectors.toSet()));
model.addAttribute("allPermissions",PermissionsArray.all());
model.addAttribute("cmsSettings", CmsSettings.getInstance());
model.addAttribute("isManager", DynamicGroup.get("managers").isMember(
Authenticate.getUser()));
model.addAttribute("templates", Site.getTemplates());
model.addAttribute("themes", Bennu.getInstance().getCMSThemesSet().stream()
.sorted(Comparator.comparing(CMSTheme::getName)).collect(Collectors.toList()));
return "fenixedu-cms/manageSearch";
}
@RequestMapping("/{slug}")
public String manage(Model model, @PathVariable String slug) throws IOException {
Site site = Site.fromSlug(slug);
ensureCanAccess(site);
model.addAttribute("site", site);
model.addAttribute("cmsSettings", CmsSettings.getInstance());
editSiteInfo(site, model);
return "fenixedu-cms/manageSite";
}
@RequestMapping(value = "/{slug}/analytics", method = RequestMethod.GET, produces = JSON)
public
@ResponseBody
String viewSiteAnalyticsData(@PathVariable String slug) {
Site site = Site.fromSlug(slug);
ensureCanDoThis(site, Permission.MANAGE_ANALYTICS);
return site.getAnalytics().getOrFetch(site).toString();
}
private Analytics getUserAnalytics() {
GoogleCredential credential =
GoogleAPI.getInstance().getAuthenticatedUser(Authenticate.getUser()).get().getAuthenticatedSDK();
return new Analytics.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential).setApplicationName(
PortalConfiguration.getInstance().getApplicationTitle().getContent(Locale.ENGLISH)).build();
}
@RequestMapping(value = "/{slug}/export", method = RequestMethod.GET)
public void export(@PathVariable String slug, HttpServletResponse response) {
Site site = Site.fromSlug(slug);
CmsSettings.getInstance().ensureCanManageSettings();
try {
response.setContentType(ZIP_MIME_TYPE);
response.setHeader("Content-Disposition", "attachment;filename=" + FilenameUtils.normalize(slug + ".zip"));
new SiteExporter(site).export().writeTo(response.getOutputStream());
//TODO - show success message
} catch (IOException e) {
//TODO - show error message
throw new RuntimeException("Error exporting site " + slug, e);
}
}
@RequestMapping(value = "/import", method = RequestMethod.POST)
public RedirectView importSite(@RequestParam MultipartFile attachment) {
CmsSettings.getInstance().ensureCanManageSettings();
try {
siteImport(attachment);
//TODO - show success message
} catch (Exception e) {
//TODO - show error message
throw new RuntimeException("Error importing site ", e);
}
return new RedirectView("/cms/sites/", true);
}
@Atomic(mode = TxMode.WRITE)
private Site siteImport(MultipartFile siteZipFile) throws Exception {
File tempFile = File.createTempFile(UUID.randomUUID().toString(), ".zip");
Files.write(siteZipFile.getBytes(), tempFile);
Site site = new SiteImporter(new ZipFile(tempFile)).importSite();
SiteActivity.importedSite(site, Authenticate.getUser());
return site;
}
private List<Site> getSites() {
return getSites(Bennu.getInstance().getSitesSet());
}
private List<Site> getSites(Collection<Site> sites) {
User user = Authenticate.getUser();
return sites.stream().filter(x -> PermissionEvaluation.canAccess(user, x)).sorted(Comparator.comparing(Site::getCreationDate).reversed())
.collect(toList());
}
@RequestMapping(value = "{slug}/edit", method = RequestMethod.POST)
public RedirectView edit(@PathVariable(value = "slug") String slug, @RequestParam LocalizedString name,
@RequestParam LocalizedString description, @RequestParam(required = false) String theme, @RequestParam(
required = false) String newSlug, @RequestParam(required = false, defaultValue = "") String initialPageSlug,
@RequestParam(required = false, defaultValue = "false") Boolean published,
@RequestParam(required = false) String viewGroup, @RequestParam(required = false) String folder, @RequestParam(
required = false) String analyticsCode, @RequestParam(required = false) String accountId,
RedirectAttributes redirectAttributes) {
if (name.isEmpty()) {
redirectAttributes.addFlashAttribute("emptyName", true);
return new RedirectView("/sites/" + slug + "/edit", true);
} else {
Site s = Site.fromSlug(slug);
newSlug = Optional.ofNullable(newSlug).orElse(slug);
if(canAccess(Authenticate.getUser(),s)) {
editSite(name, description, theme, newSlug, published, s, viewGroup, folder, analyticsCode, accountId,
s.pageForSlug(initialPageSlug));
}
return new RedirectView("/cms/sites/" + newSlug + "#general", true);
}
}
public void editSiteInfo(Site site, Model model) {
if (PermissionEvaluation.canDoThis(site, Permission.EDIT_SITE_INFORMATION)) {
if (CmsSettings.getInstance().canManageThemes()) {
model.addAttribute("themes", Bennu.getInstance().getCMSThemesSet());
}
if (CmsSettings.getInstance().canManageFolders()) {
model.addAttribute("folders", Bennu.getInstance().getCmsFolderSet());
}
if (CmsSettings.getInstance().canManageSettings()) {
model.addAttribute("defaultSite", Bennu.getInstance().getDefaultSite());
}
if (PermissionEvaluation.canDoThis(site, Permission.MANAGE_ANALYTICS)) {
model.addAttribute("google", GoogleAPI.getInstance());
GoogleAPI.getInstance().getAuthenticatedUser(Authenticate.getUser())
.ifPresent(googleUser -> {
Analytics analytics = getUserAnalytics();
List<GoogleAccountBean> googleAccountBeans = new ArrayList<>();
try {
Accounts accounts = analytics.management().accounts().list().execute();
for (Account account : accounts.getItems()) {
Webproperties
properties =
analytics.management().webproperties().list(account.getId())
.execute();
googleAccountBeans.add(new GoogleAccountBean(account, properties));
}
model.addAttribute("googleUser", googleUser);
model.addAttribute("accounts", googleAccountBeans);
} catch (GoogleJsonResponseException e) {
LOGGER.error("Error loading analytics properties", e);
if (e.getDetails().getCode() == 401) {
//Invalid credentials -> remove invalid user
FenixFramework.atomic(() -> googleUser.delete());
}
} catch (IOException e) {
LOGGER.error("Error loading analytics properties", e);
}
});
}
}
}
@RequestMapping(value = "{siteSlug}/clone", method = RequestMethod.POST)
public RedirectView clone(@PathVariable String siteSlug) {
Site s = Site.fromSlug(siteSlug);
CmsSettings.getInstance().ensureCanManageSettings();
ensureCanAccess(s);
Site newSite = cloneSite(s);
//TODO - add success message
return new RedirectView("/cms/sites/", true);
}
@Atomic(mode = TxMode.WRITE)
private Site cloneSite(Site originalSite) {
Site clone = originalSite.clone(new CloneCache());
clone.setName(originalSite.getName().append(" (clone)"));
SiteActivity.clonedSite(clone, Authenticate.getUser());
return clone;
}
@Atomic(mode = TxMode.WRITE)
private void editSite(LocalizedString name, LocalizedString description, String theme, String slug, Boolean published,
Site s, String viewGroup, String folder, String analyticsCode, String accountId, Page initialPage) {
if (PermissionEvaluation.canDoThis(s, Permission.EDIT_SITE_INFORMATION)) {
s.setName(Sanitization.strictSanitize(name));
s.setDescription(Sanitization.sanitize(description));
}
if (PermissionEvaluation.canDoThis(s, Permission.CHANGE_THEME)) {
s.setThemeType(Optional.ofNullable(theme).orElseGet(() -> s.getThemeType()));
}
if (PermissionEvaluation.canDoThis(s, Permission.CHOOSE_PATH_AND_FOLDER)) {
slug = Optional.ofNullable(slug).orElseGet(() -> s.getSlug());
if (!Strings.isNullOrEmpty(folder)) {
s.setFolder(FenixFramework.getDomainObject(folder));
} else if (s.getFolder() != null) {
// Remove the folder and set the new slug, so the MenuFunctionality
// will be created
s.setFolder(null);
s.setSlug(slug);
s.updateMenuFunctionality();
}
if (!s.getSlug().equals(slug)) {
s.setSlug(slug);
s.updateMenuFunctionality();
}
}
if (PermissionEvaluation.canDoThis(s, Permission.MANAGE_ANALYTICS)) {
Optional<GoogleUser> googleUser = GoogleAPI.getInstance().getAuthenticatedUser(Authenticate.getUser());
if ((Strings.isNullOrEmpty(analyticsCode) || Strings.isNullOrEmpty(accountId)) && googleUser.isPresent()) {
googleUser.get().delete();
}
s.setAnalyticsCode(analyticsCode);
s.setAnalyticsAccountId(accountId);
s.getAnalytics().update(s);
}
if (PermissionEvaluation.canDoThis(s, Permission.PUBLISH_SITE)) {
s.setPublished(published);
s.setCanViewGroup(Group.parse(viewGroup));
}
if (PermissionEvaluation.canDoThis(s, Permission.CHOOSE_DEFAULT_PAGE)) {
s.setInitialPage(initialPage);
}
Signal.emit(Site.SIGNAL_EDITED, new DomainObjectEvent<>(s));
}
@RequestMapping(value = "{slug}/delete", method = RequestMethod.POST)
public RedirectView delete(@PathVariable(value = "slug") String slug) {
Site s = Site.fromSlug(slug);
CmsSettings.getInstance().ensureCanManageSettings();
s.delete();
return new RedirectView("/cms/sites", true);
}
@RequestMapping(value = "cmsSettings", method = RequestMethod.POST)
public RedirectView editSettings(@RequestParam(required = false) String themesManagers, @RequestParam(required = false) String rolesManagers,
@RequestParam(required = false) String foldersManagers, @RequestParam(required = false) String settingsManagers) {
FenixFramework.atomic(() -> {
if (DynamicGroup.get("managers").isMember(Authenticate.getUser())) {
CmsSettings settings = CmsSettings.getInstance().getInstance();
settings.ensureCanManageGlobalPermissions();
settings.setThemesManagers(group(themesManagers));
settings.setRolesManagers(group(rolesManagers));
settings.setFoldersManagers(group(foldersManagers));
settings.setSettingsManagers(group(settingsManagers));
}
});
return new RedirectView("/cms/sites", true);
}
@RequestMapping(value = "defaultSite", method = RequestMethod.POST)
public RedirectView editDefaultSite(@RequestParam String slug) {
FenixFramework.atomic(() -> {
if (Bennu.getInstance().getDefaultSite() == null || !Bennu.getInstance().getDefaultSite().getSlug().equals(slug)) {
CmsSettings.getInstance().ensureCanManageSettings();
Site s = Site.fromSlug(slug);
if (s != null) {
Bennu.getInstance().setDefaultSite(s);
}
}
});
return new RedirectView("/cms/sites", true);
}
private static PersistentGroup group(String expression) {
Group group = Group.parse(expression);
if (!group.isMember(Authenticate.getUser())) {
CmsDomainException.forbiden();
}
return group.toPersistentGroup();
}
@RequestMapping(value = "/{slugSite}/roles", method = RequestMethod.GET)
public String viewSiteRoles(@PathVariable String slugSite, Model model) {
Site site = Site.fromSlug(slugSite);
ensureCanDoThis(site, Permission.MANAGE_ROLES);
Set<RoleTemplate> siteTemplates = site.getRolesSet().stream().map(Role::getRoleTemplate).collect(Collectors.toSet());
model.addAttribute("site", site);
model.addAttribute("roles",
site.getRolesSet().stream().sorted(Comparator.comparing(Role::getName)).collect(Collectors.toList()));
return "fenixedu-cms/editSiteRoles";
}
@RequestMapping(value = "/{slugSite}/roles/{roleId}/edit", method = RequestMethod.GET)
public String viewEditRole(@PathVariable String slugSite, @PathVariable String roleId, Model model) {
PermissionEvaluation.canAccess(Authenticate.getUser(), Site.fromSlug(slugSite));
model.addAttribute("role", FenixFramework.getDomainObject(roleId));
model.addAttribute("cmsSettings", CmsSettings.getInstance());
return "fenixedu-cms/editRole";
}
@RequestMapping(value = "/{slugSite}/roles/{roleId}/change", method = RequestMethod.POST)
public RedirectView changeSiteRole(@PathVariable String slugSite, @PathVariable String roleId, @RequestParam String group) {
editRole(slugSite, roleId, group);
return new RedirectView("/cms/sites/" + slugSite + "#roles", true);
}
@RequestMapping(value = "/{slugSite}/roles/{roleId}/edit", method = RequestMethod.POST)
public RedirectView editSiteRole(@PathVariable String slugSite, @PathVariable String roleId, @RequestParam String group) {
editRole(slugSite, roleId, group);
return new RedirectView("/cms/sites/" + slugSite + "/roles/" + roleId + "/edit", true);
}
private void editRole(@PathVariable String slugSite, @PathVariable String roleId, @RequestParam String group) {
FenixFramework.atomic(() -> {
Site site = Site.fromSlug(slugSite);
PermissionEvaluation.canDoThis(site, Permission.MANAGE_ROLES);
Role role = FenixFramework.getDomainObject(roleId);
role.setGroup(Group.parse(group).toPersistentGroup());
Signal.emit(Role.SIGNAL_EDITED, new DomainObjectEvent<>(role));
});
}
@RequestMapping(value = "/{slugSite}/roles/add", method = RequestMethod.POST)
public RedirectView createSiteRole(@PathVariable String slugSite, @RequestParam String roleTemplateId) {
FenixFramework.atomic(() -> {
Site site = Site.fromSlug(slugSite);
ensureCanDoThis(site, Permission.MANAGE_ROLES);
RoleTemplate template = FenixFramework.getDomainObject(roleTemplateId);
if (!template.getRolesSet().stream().map(Role::getSite).filter(roleSite -> roleSite.equals(site)).findAny()
.isPresent()) {
new Role(template, site);
}
});
return new RedirectView("/cms/sites/" + slugSite + "/roles/" + roleTemplateId + "/edit", true);
}
@RequestMapping(value = "/{slugSite}/roles/{roleId}/delete", method = RequestMethod.POST)
public RedirectView removeRole(@PathVariable String slugSite, @PathVariable String roleId) {
CmsSettings.getInstance().ensureCanManageRoles();
FenixFramework.atomic(() -> ((Role) FenixFramework.getDomainObject(roleId)).delete());
return new RedirectView("/cms/sites/" + slugSite + "/roles", true);
}
public static class GoogleAccountBean {
private final Account account;
private final Webproperties properties;
public GoogleAccountBean(Account account, Webproperties properties) {
this.account = account;
this.properties = properties;
}
public String getName() {
return account.getName();
}
public String getId() {
return account.getId();
}
public List<GoogleAccountProperty> getProperties() {
return properties.getItems().stream().map(GoogleAccountProperty::new).collect(toList());
}
}
public static class GoogleAccountProperty {
private final Webproperty webproperty;
public GoogleAccountProperty(Webproperty webproperty) {
this.webproperty = webproperty;
}
public String getName() {
return webproperty.getName();
}
public String getId() {
return webproperty.getId();
}
}
}