/*
*
* * Copyright (c) 2016. David Sowerby
* *
* * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* * the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* * specific language governing permissions and limitations under the License.
*
*/
package uk.q3c.krail.core.navigate;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* This provides a more strict interpretation of the UriFragment than Vaadin does by default. It requires that the URI
* structure is of the form:<br>
* <br>
* http://example.com/domain#!finance/report/risk/id=1223/year=2012 (<I>with or without the bang after the hash,
* depending on the <code>useBang</code> setting)</I> <br>
* <br>
* where: <br>
* <br>
* finance/report/risk/ <br>
* <br>
* is a "virtual page path" and is represented by a View <br>
* <br>
* and everything after it is paired parameters. If a segment within what should be paired parameters is malformed, it
* is ignored, and when the URI is reconstructed, will disappear. So for example: <br>
* <br>
* <code>http://example.com/domain#!finance/report/risk/id=1223/year2012 <br></code> <br>
* would be treated as: <br>
* <br>
* <code>http://example.com/domain#!finance/report/risk/id=1223</code><br>
* The year parameter has been dropped because it has no "=" <br>
* <br>
* Optionally uses hash(#) or hashBang(#!). Some people get excited about hashbangs. Try Googling it<br>
* <br>
*/
public class StrictURIFragmentHandler implements URIFragmentHandler, Serializable {
private boolean useBang = false;
@Inject
public StrictURIFragmentHandler() {
super();
}
@Override
public boolean isUseBang() {
return useBang;
}
@Override
public void setUseBang(boolean useBang) {
if (this.useBang != useBang) {
this.useBang = useBang;
}
}
/**
* Creates and returns a {@link NavigationState} with elements of the {@code uri} decoded. The "virtual page" is
* assumed to finish as soon as a paired parameter is found. No attempt is made to validate the actual structure of
* the path, so for example something like <code>view//subview/a=b</code> will result in a virtual page of
* <code>view//subview</code>. If <code>uri</code> is null or empty, the uri is consider to be an empty String. If
* <code>navigationState</code> contains only paired parameters, the virtual page is set to an empty string.
*/
@Override
public NavigationState navigationState(String uri) {
NavigationState navigationState = new NavigationState();
String fragment = StringUtils.isEmpty(uri) ? "" : stripBangAndTrailingSlash(uri);
navigationState.fragment(fragment);
updateParts(navigationState);
return navigationState;
}
@Override
public void updateParts(NavigationState navigationState) {
navigationState.setUpdateInProgress(true);
String fragment = navigationState.getFragment();
List<String> pathSegments = new ArrayList<>();
// no parameters, everything is the virtual page path
// if (!fragment.contains("=")) {
// navigationState.setVirtualPage(fragment);
// return navigationState;
// }
Iterable<String> segments = Splitter.on('/')
.split(fragment);
boolean paramsStarted = false;
Iterator<String> iter = segments.iterator();
while (iter.hasNext()) {
String s = iter.next();
if (paramsStarted) {
addParameter(navigationState, s);
} else {
if (s.contains("=")) {
paramsStarted = true;
addParameter(navigationState, s);
} else {
pathSegments.add(s);
}
}
}
navigationState.setUpdateInProgress(true);
// join the virtual page path up again
navigationState.pathSegments(pathSegments);
validateSegments(navigationState);
navigationState.updated();
}
private String virtualPageFromSegments(List<String> pathSegments) {
return
Joiner.on('/')
.join(pathSegments.toArray());
}
private void addParameter(NavigationState navigationState, String s) {
if (s.contains("=")) {
Iterable<String> segments = Splitter.on('=')
.split(s);
Iterator<String> iter = segments.iterator();
String key = iter.next();
String value = iter.next();
if (Strings.isNullOrEmpty(key)) {
return;
}
if (Strings.isNullOrEmpty(value)) {
return;
}
navigationState.addParameter(key, value);
}
}
private String stripBangAndTrailingSlash(String path) {
int copyStart = 0;
int copyEnd = path.length();
if (path.startsWith("!")) {
copyStart++;
if (path.charAt(1) == '/') {
copyStart++;
}
} else {
if (path.startsWith("/")) {
copyStart++;
}
}
if (path.endsWith("/")) {
copyEnd--;
}
return path.substring(copyStart, copyEnd);
}
/**
* Updates the fragment in {@code navigationState} from the component parts of {@code navigationState}
*
* @see uk.q3c.krail.core.navigate.URIFragmentHandler#updateFragment(uk.q3c.krail.core.navigate.NavigationState)
*/
@Override
public void updateFragment(NavigationState navigationState) {
navigationState.setUpdateInProgress(true);
navigationState.fragment(fragment(navigationState));
validateSegments(navigationState);
navigationState.updated();
}
private void validateSegments(NavigationState navigationState) {
if (navigationState.getVirtualPage() == null) {
String virtualPage = virtualPageFromSegments(navigationState.getPathSegments());
navigationState.virtualPage(virtualPage);
} else if (navigationState.getPathSegments()
.isEmpty()) {
navigationState.pathSegments(segmentsFromVirtualPage(navigationState.getVirtualPage()));
}
}
private List<String> segmentsFromVirtualPage(String virtualPage) {
return ImmutableList.copyOf(Splitter.on('/')
.split(virtualPage));
}
@Override
public String fragment(NavigationState navigationState) {
StringBuilder buf = new StringBuilder();
if (useBang) {
buf.append('!');
}
String vp = navigationState.getVirtualPage() == null ? virtualPageFromSegments(navigationState.getPathSegments()) : navigationState.getVirtualPage();
buf.append(vp);
// append the parameters
for (Map.Entry<String, String> entry : navigationState.getParameters()
.entrySet()) {
buf.append('/');
buf.append(entry.getKey());
buf.append('=');
buf.append(entry.getValue());
}
return buf.toString();
}
}