Pergunta

I have a ListView composed by different fragment which contains text and a link (inside another fragment). The link is visible depending on the state of the listview model.

For simplicity let's say the link is visible depending on a boolean field of the listview model, if it's true is visible, invisible otherwise.

At first the link is visible, I copy the link location (encrypted), I wait for my model to change (i.e. boolean to false) and after I refresh the page the link is gone. (correct!)

If I try to give the URL (copied before) back in the browser I receive a WicketRuntimeException telling me that the listener for this link was not found.

To be more complete the link is inside a fragment:

<wicket:fragment wicket:id="reservationRatingFragment">
    <li>
        <div>
            <img src="/img/good.png" />
        </div>
        <p>
            <a wicket:id="ratingGoodLink" href="#"> <wicket:message
                    key="messaging.reservation.rating.good" />
            </a>
        </p>
    </li>
</wicket:fragment>

And when I say invisible I mean that I set the markup container of the fragment as .setVisible(false);

Why is this happening? I'm supposing that if I recall a link which is not visible anymore the framework should just skip it and refresh the page I'm currently on (or redirect me to the base page).

If for example I copy the link and I change BasePage (go to the homepage for example), the exception still occurs when I'm recalling the copied URL.

EDITED:

In the first fragment:

WebMarkupContainer msgRatingContainer = new WebMarkupContainer("messageRatingContainer") {
            private static final long serialVersionUID = 1L;

            @Override
            public void onConfigure() {
                setVisible(message.getType() == MessageType.RATING);
            }
        };

if (msgRatingContainer.isVisible()) {
            if (message.getType() == MessageType.RATING) {
                msgRatingContainer.add(new ReservationRatingFragment("messageRatingSection",
                        "reservationRatingFragment", this, item, message));
}

The nested fragment (ReservationRatingFragment):

public ReservationRatingFragment(String id, String markupId,MarkupContainer markupContainer, Item item, Message msg) {
        super(id, markupId, markupContainer, new Model<Message>(msg));
        /* Avoid render container */
        setRenderBodyOnly(true);

        /* Load button components */
        Link<Void> ratingGoodLink = new Link<Void>("ratingGoodLink"){
            private static final long serialVersionUID = 1L;

            @Override
            public void onClick() {
                processRating(ReservationEvaluationResult.GOOD);
            }   
        };
        add(ratingGoodLink);

        Link<Void> ratingBadLink = new Link<Void>("ratingBadLink"){
            private static final long serialVersionUID = 1L;
            @Override
            public void onClick() {
                processRating(ReservationEvaluationResult.BAD);
            }   
        };
        add(ratingBadLink);
    }

Markup for both fragments:

<wicket:fragment wicket:id="messageFragment">
    Some content...
    <!-- Here goes my fragment with link -->
    <ul wicket:id="messageRatingContainer">
        <div wicket:id="messageRatingSection"></div>
    </ul>

    <wicket:fragment wicket:id="reservationRatingFragment">
        <li><div>
                <img src="/img/messaging/good.png" />
            </div>
            <p>
                <a wicket:id="ratingGoodLink" href="#"> <wicket:message
                        key="messaging.reservation.rating.good" />
                </a>
            </p></li>
        <li><div>
                <img src="/img/messaging/bad.png" />
            </div>
            <p>
                <a wicket:id="ratingBadLink" href="#"> <wicket:message
                        key="messaging.reservation.rating.bad" />
                </a>
            </p></li>
    </wicket:fragment>
</wicket:fragment>

EDITED: The processRating just perform a call to a controller (which handle the change in the backend). In the controller I check for the replay attack (if this action is already performed) and if so I throw a runtime exception that lead the user to a warning page (You already rated this message). The fact is, in this case it don't get to this point, since the link is not available it doesn't call the controller and it just throw the InvalidUrlException since the link is not visible.

Wicket version: 1.4.19

Thanks

Foi útil?

Solução 3

The only viable solution I found was to extend the RequestCycle and override the onRuntimeException method this way:

@Override
public Page onRuntimeException(Page page, RuntimeException e) {     
    if(e instanceof InvalidUrlException) {
        return new HomePage();
    } else {
        return super.onRuntimeException(page, e);
    }
}

Outras dicas

Your assumption that an invalid link will be ignored or redirect you to the base page is wrong.

Why is that?

If we take a step back, what happens when you click a link? The state of your application changes. However this is only safe to do so if the application is in the state it was when the link was created. If this rule wasn't enforced, you would need to make sure that every single potential state transition is either acceptable or explicitly marked as invalid. This would be highly impractical, if not impossible in most systems. But neglecting this would not only be a security risk but it could result in corrupt data.

It's best to think of it as a case of optimistic locking. (Mostly because it is :)) When the link is created, it is given the version number of the internal state at the time of creation. When the link is clicked, that version number is compared to the current version of the internal state. If the two match, the link is accepted as valid, the internal state is updated and its version number is incremented. If the two numbers don't match, the link is rejected and an exception is thrown because an invalid state transition attempt can't be ignored.

I won't explain how to get around this limitation as it's already been told in another answer, I just wanted to answer the "why" question.

I am not sure I understand the exact reason for your implementation. That said, I would recommend using the BookmarkablePageLink() with PageParameters set to perform your processRating() method upon loading the destination page.

Add your link components:

    PageParameters ppGood = new PageParameters("0="+ReservationEvaluationResult.GOOD);
    PageParameters ppBad = new PageParameters("0="+ReservationEvaluationResult.BAD);
    add(new BookmarkablePageLink("ratingGoodLink", DestinationPage.class, ppGood));
    add(new BookmarkablePageLink("ratingBadLink", DestinationPage.class, ppBad));

Then create a new constructor in your DestinationPage:

public class DestinationPage extends WebPage {
    public DestinationPage(PageParameters param) {
        if(param.getString("0")!=null){
            String rating = param.getString("0");
            processRating(rating);
        }
     ...

This will give you a link that will be persistent and should allow you to copy and paste the URL.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top