public ProcessSelectionPage(
      final WPSAccessRulePage wpsAccessRulePage, final ProcessGroupInfo pfi) {
    this.pfi = pfi;

    // prepare the process factory title
    Class<? extends ProcessFactory> factoryClass = pfi.getFactoryClass();
    ProcessFactory pf = GeoServerProcessors.getProcessFactory(factoryClass, false);
    if (pf == null) {
      throw new IllegalArgumentException("Failed to locate the process factory " + factoryClass);
    }
    this.title = pf.getTitle().toString(getLocale());

    Form form = new Form("form");
    add(form);

    GeoServerRoleService roleService = getSecurityManager().getActiveRoleService();
    try {
      for (GeoServerRole r : roleService.getRoles()) {
        availableRoles.add(r.getAuthority());
      }
    } catch (IOException e1) {
      LOGGER.log(Level.FINER, e1.getMessage(), e1);
    }

    final FilteredProcessesProvider provider = new FilteredProcessesProvider(pfi, getLocale());
    final AutoCompleteSettings settings = new AutoCompleteSettings();
    settings.setShowCompleteListOnFocusGain(false);
    settings.setShowListOnEmptyInput(true);
    settings.setShowListOnFocusGain(true);
    settings.setMaxHeightInPx(100);
    processSelector =
        new GeoServerTablePanel<FilteredProcess>("selectionTable", provider) {

          @Override
          protected Component getComponentForProperty(
              String id,
              final IModel<FilteredProcess> itemModel,
              Property<FilteredProcess> property) {
            if (property.getName().equals("enabled")) {
              Fragment fragment = new Fragment(id, "enabledFragment", ProcessSelectionPage.this);
              CheckBox enabled =
                  new CheckBox("enabled", (IModel<Boolean>) property.getModel(itemModel));
              enabled.setOutputMarkupId(true);
              fragment.add(enabled);
              return fragment;
            } else if (property.getName().equals("title")) {
              return new Label(id, property.getModel(itemModel));
            } else if (property.getName().equals("description")) {
              return new Label(id, property.getModel(itemModel));
            } else if (property.getName().equals("roles")) {
              Fragment fragment = new Fragment(id, "rolesFragment", ProcessSelectionPage.this);
              TextArea<?> roles =
                  new TextArea("roles", property.getModel(itemModel)) {
                    public <C extends Object>
                        org.apache.wicket.util.convert.IConverter<C> getConverter(
                            java.lang.Class<C> type) {
                      return new RolesConverter(availableRoles);
                    };
                  };
              StringBuilder selectedRoles = new StringBuilder();
              IAutoCompleteRenderer<String> roleRenderer = new RolesRenderer(selectedRoles);
              AutoCompleteBehavior<String> b =
                  new RolesAutoCompleteBehavior(
                      roleRenderer, settings, selectedRoles, availableRoles);
              roles.setOutputMarkupId(true);
              roles.add(b);
              fragment.add(roles);
              return fragment;
            } else if (property.getName().equals("validated")) {
              final IModel<Boolean> hasValidatorsModel =
                  (IModel<Boolean>) property.getModel(itemModel);
              IModel<String> availableModel =
                  new AbstractReadOnlyModel<String>() {

                    @Override
                    public String getObject() {
                      Boolean value = hasValidatorsModel.getObject();
                      if (Boolean.TRUE.equals(value)) {
                        return "*";
                      } else {
                        return "";
                      }
                    }
                  };
              return new Label(id, availableModel);
            } else if (property.getName().equals("edit")) {
              Fragment fragment = new Fragment(id, "linkFragment", ProcessSelectionPage.this);
              // we use a submit link to avoid losing the other edits in the form
              Link link =
                  new Link("link") {
                    @Override
                    public void onClick() {
                      FilteredProcess fp = (FilteredProcess) itemModel.getObject();
                      setResponsePage(new ProcessLimitsPage(ProcessSelectionPage.this, fp));
                    }
                  };
              fragment.add(link);

              return fragment;
            }
            return null;
          }
        };
    processSelector.setFilterable(false);
    processSelector.setPageable(false);
    processSelector.setOutputMarkupId(true);
    form.add(processSelector);
    SubmitLink apply =
        new SubmitLink("apply") {
          @Override
          public void onSubmit() {
            // super.onSubmit();
            pfi.getFilteredProcesses().clear();
            for (FilteredProcess process : provider.getItems()) {
              if ((process.getRoles() != null && !process.getRoles().isEmpty())
                  || !process.getEnabled()
                  || (process.getValidators() != null && !process.getValidators().isEmpty())) {
                ProcessInfo pai = process.toProcessInfo();
                pfi.getFilteredProcesses().add(pai);
              }
            }
            setResponsePage(wpsAccessRulePage);
          }
        };
    form.add(apply);
    Link cancel =
        new Link("cancel") {
          @Override
          public void onClick() {
            setResponsePage(wpsAccessRulePage);
          }
        };
    form.add(cancel);
  }
  private void init() {

    add(
        new HeaderContributor(
            new IHeaderContributor() {
              private static final long serialVersionUID = 1L;

              public void renderHead(IHeaderResponse response) {
                response.renderCSSReference(
                    new CompressedResourceReference(
                        WebDataLookupField.class, "servoy_lookupfield.css")); // $NON-NLS-1$
              }
            }) {
          @Override
          public boolean isEnabled(Component component) {
            return !getScriptObject().isReadOnly() && getScriptObject().isEnabled();
          }
        });

    setOutputMarkupPlaceholderTag(true);

    AutoCompleteSettings behSettings = new AutoCompleteSettings();
    behSettings.setMaxHeightInPx(200);
    behSettings.setPreselect(true);
    behSettings.setShowCompleteListOnFocusGain(true);
    behSettings.setAdjustInputWidth(false);

    ClientProperties clp =
        (application.getApplicationType() != IApplication.HEADLESS_CLIENT
            ? ((WebClientInfo) Session.get().getClientInfo()).getProperties()
            : null); // in case of batch processors/jsp, we can't get browser info because UI is not
                     // given by web client components
    if (clp != null && (!clp.isBrowserInternetExplorer() || clp.getBrowserVersionMajor() >= 8)) {
      // smart positioning doesn't work on IE < 8 (probably because of unreliable
      // clientWidth/clientHeight browser element js properties)
      behSettings.setUseSmartPositioning(true);
      behSettings.setUseHideShowCoveredIEFix(
          false); // don't know if the problem this setting is for can still be reproduced (I
                  // couldn't reproduce it)... this is true by default and makes fields in IE and
                  // Opera appear/dissapear if they would be covered by type-ahead popup
    } else {
      behSettings.setUseSmartPositioning(false);
      behSettings.setUseHideShowCoveredIEFix(true);
    }
    behSettings.setThrottleDelay(500);

    IAutoCompleteRenderer<Object> renderer =
        new IAutoCompleteRenderer<Object>() {
          protected String getTextValue(Object object) {
            String str = ""; // $NON-NLS-1$
            if (object instanceof DisplayString) {
              str = object.toString();
            } else if (object != null && !(object instanceof String)) {
              IConverter con = getConverter(object.getClass());
              if (con != null) {
                str = con.convertToString(object, getLocale());
              } else {
                str = object.toString();
              }
            } else if (object != null) {
              str = object.toString();
            }
            if (str == null || str.trim().equals("")) str = "&nbsp;"; // $NON-NLS-1$//$NON-NLS-2$
            return str;
          }

          protected void renderChoice(Object object, Response response, String criteria) {
            if (IValueList.SEPARATOR_DESIGN_VALUE.equals(object)) return;
            String renderedObject = getTextValue(object);
            // escape the markup if it is not html or not just an empty none breaking space (null or
            // empty string object)
            if (!renderedObject.equals("&nbsp;") && !HtmlUtils.hasHtmlTag(renderedObject))
              renderedObject =
                  HtmlUtils.escapeMarkup(
                          renderedObject,
                          true, //$NON-NLS-1$
                          false)
                      .toString();
            response.write(renderedObject);
          }

          /*
           * (non-Javadoc)
           *
           * @see org.apache.wicket.extensions.ajax.markup.html.autocomplete.IAutoCompleteRenderer#render(java.lang.Object, org.apache.wicket.Response,
           * java.lang.String)
           */
          public void render(Object object, Response response, String criteria) {
            String textValue = getTextValue(object);
            if (textValue == null) {
              throw new IllegalStateException(
                  "A call to textValue(Object) returned an illegal value: null for object: "
                      + object.toString());
            }
            textValue = textValue.replaceAll("\\\"", "&quot;");

            response.write("<li textvalue=\"" + textValue + "\"");
            response.write(">");
            renderChoice(object, response, criteria);
            response.write("</li>");
          }

          /*
           * (non-Javadoc)
           *
           * @see org.apache.wicket.extensions.ajax.markup.html.autocomplete.IAutoCompleteRenderer#renderHeader(org.apache.wicket.Response)
           */
          @SuppressWarnings("nls")
          public void renderHeader(Response response) {
            StringBuffer listStyle = new StringBuffer();
            listStyle.append("style=\"");

            String fFamily = "Tahoma, Arial, Helvetica, sans-serif";
            String bgColor = "#ffffff";
            String fgColor = "#000000";
            String fSize = TemplateGenerator.DEFAULT_FONT_SIZE + "px";
            String padding = "2px";
            String margin = "0px";
            if (getFont() != null) {
              Font f = getFont();
              if (f != null) {
                if (f.getFamily() != null) {
                  fFamily = f.getFamily();
                  if (fFamily.contains(" ")) fFamily = "'" + fFamily + "'";
                }
                if (f.getName() != null) {
                  String fName = f.getName();
                  if (fName.contains(" ")) fName = "'" + fName + "'";
                  fFamily = fName + "," + fFamily;
                }
                if (f.isBold()) listStyle.append("font-weight:bold; ");
                if (f.isItalic()) listStyle.append("font-style:italic; ");

                fSize = Integer.toString(f.getSize()) + "px";
              }
            }

            if (getListColor() != null && getListColor().getAlpha() == 255) {
              // background shouldn't be transparent
              bgColor = getWebColor(getListColor().getRGB());
            }
            if (getForeground() != null) {
              fgColor = getWebColor(getForeground().getRGB());
            }
            Insets _padding = getPadding();
            if (getPadding() != null)
              padding =
                  _padding.top
                      + "px "
                      + _padding.right
                      + "px "
                      + _padding.bottom
                      + "px "
                      + _padding.left
                      + "px";

            listStyle.append("font-family:" + fFamily + "; ");
            listStyle.append("background-color: " + bgColor + "; ");
            listStyle.append("color: " + fgColor + "; ");
            listStyle.append("font-size:" + fSize + "; ");
            listStyle.append(
                "min-width:" + (getSize().width - 6) + "px; "); // extract padding and border
            listStyle.append("margin: " + margin + "; ");
            listStyle.append("padding: " + padding + "; ");
            listStyle.append(
                "text-align:"
                    + TemplateGenerator.getHorizontalAlignValue(getHorizontalAlignment()));
            listStyle.append("\"");

            response.write("<ul " + listStyle + ">");
          }

          /*
           * (non-Javadoc)
           *
           * @see org.apache.wicket.extensions.ajax.markup.html.autocomplete.IAutoCompleteRenderer#renderFooter(org.apache.wicket.Response)
           */
          public void renderFooter(Response response) {
            response.write("</ul>"); // $NON-NLS-1$
          }

          /**
           * Returns web color representation of int rgba color by removing the alpha value
           *
           * @param color int representation of rgba color
           * @return web color of form #rrggbb
           */
          private String getWebColor(int color) {
            String webColor = Integer.toHexString(color);
            int startIdx = webColor.length() - 6;
            if (startIdx < 0) startIdx = 0;
            webColor = webColor.substring(startIdx);

            StringBuilder sb = new StringBuilder();
            sb.append('#');
            int nrMissing0 = 6 - webColor.length();
            for (int i = 0; i < nrMissing0; i++) {
              sb.append('0');
            }
            sb.append(webColor);

            return sb.toString();
          }
        };

    AutoCompleteBehavior<Object> beh =
        new AutoCompleteBehavior<Object>(renderer, behSettings) {
          private static final long serialVersionUID = 1L;

          /**
           * @see
           *     org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteBehavior#getChoices(java.lang.String)
           */
          @Override
          protected Iterator<Object> getChoices(String input) {
            String filteredInput = filterInput(input);
            if (changeListener != null) dlm.getValueList().removeListDataListener(changeListener);
            try {
              dlm.fill(parentState, getDataProviderID(), filteredInput, false);
              return dlm.iterator();
            } catch (Exception ex) {
              Debug.error(ex);
            } finally {
              if (changeListener != null) dlm.getValueList().addListDataListener(changeListener);
            }
            return Collections.emptyList().iterator();
          }

          /** filters the input in case of masked input (removes the mask) */
          private String filterInput(String input) {
            String displayFormat = WebDataLookupField.this.parsedFormat.getDisplayFormat();
            if (displayFormat != null
                && displayFormat.length() > 0
                && input.length() == displayFormat.length()) {
              int index = firstBlankSpacePosition(input, displayFormat);
              if (index == -1) return input;
              return input.substring(0, index);
            }
            return input;
          }

          /**
           * Computes the index of the first space char found in the input and is not ' ' nor '*' in
           * the format Example: input '12 - 3 - ' format '## - ## - #' returns 6
           *
           * @param input
           * @param displayFormat
           * @return The index of the first space char found in the input and is not ' ' nor '*' in
           *     the format
           */
          private int firstBlankSpacePosition(String input, String displayFormat) {
            for (int i = 0; i < input.length(); i++) {
              if ((input.charAt(i) == ' ')
                  && (displayFormat.charAt(i) != ' ')
                  && (displayFormat.charAt(i) != '*')) return i;
            }
            return 0;
          }

          /** @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#getFailureScript() */
          @Override
          protected CharSequence getFailureScript() {
            return "onAjaxError();"; //$NON-NLS-1$
          }

          /** @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#getPreconditionScript() */
          @Override
          protected CharSequence getPreconditionScript() {
            return "onAjaxCall();" + super.getPreconditionScript(); // $NON-NLS-1$
          }

          // need to set this behavior to true (enterHidesWithNoSelection) because otherwise the
          // onKeyDown events
          // or other events for the component with type ahead would be null in Firefox, and would
          // not execute as
          // expected on the other browsers...
          @Override
          public void renderHead(IHeaderResponse response) {
            settings.setShowListOnEmptyInput(
                Boolean.TRUE.equals(
                    UIUtils.getUIProperty(
                        getScriptObject(),
                        application,
                        IApplication.TYPE_AHEAD_SHOW_POPUP_WHEN_EMPTY,
                        Boolean.TRUE)));
            settings.setShowListOnFocusGain(
                Boolean.TRUE.equals(
                    UIUtils.getUIProperty(
                        getScriptObject(),
                        application,
                        IApplication.TYPE_AHEAD_SHOW_POPUP_ON_FOCUS_GAIN,
                        Boolean.TRUE)));
            if (!getScriptObject().isReadOnly() && getScriptObject().isEnabled()) {
              super.renderHead(response);
              response.renderJavascript(
                  "Wicket.AutoCompleteSettings.enterHidesWithNoSelection = true;",
                  "AutocompleteSettingsID"); //$NON-NLS-1$ //$NON-NLS-2$
            }
          }

          /**
           * @see org.apache.wicket.behavior.AbstractBehavior#isEnabled(org.apache.wicket.Component)
           */
          @Override
          public boolean isEnabled(Component component) {
            IFormUIInternal<?> formui = findParent(IFormUIInternal.class);
            if (formui != null && formui.isDesignMode()) {
              return false;
            }
            return super.isEnabled(component) && WebClientSession.get().useAjax();
          }
        };
    add(beh);
  }