Problema:

Tenemos un Spring REST controller como este:

	@RequestMapping( 
			value = "/slots/{nodePath}/{slot}.json",  
			method = RequestMethod.DELETE,  
			produces = "application/json") 
	@ResponseBody 
	public ContentSlot deleteSlot( 
	        @RequestParam(value = "format", required = false) final String format, 
	        @RequestParam(value = "geo", required = false) final String geo, 
	        @RequestParam(value = "segment", required = false) final String segment, 
	        @RequestParam(value = "version", required = false) final String version, 
	        @PathVariable final String slot, 
	        @PathVariable final String nodePath, 
	        HttpServletRequest request, 
	        Principal principal 
	                    ) { 
                  .....
       }

Ojo en la linea que trae algo como esto:

@PathVariable final String nodePath,

Se quiere validar la cadena “nodePath”, pero no se desea tener un metodo “validador” en cada Controller que reciba esa cadena y que la valide (o transforme) en lo requerido.

Se desea que para cuando recuperemos el valor de tal objeto, este ya este validado/transformado en lo que realmente se requiere.

Para ello, hacemos lo siguiente:

1) Creamos esta anotación:

/**
 * Annotate any custom converter in the application with @UiParamConverter so it can be discovered
 * at runtime and registered
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier
@Component
public @interface UiParamConverter {
}

2) Creamos esta clase convertidora:

@UiParamConverter
public class NodeConverter implements Converter {

	/**
	 * This NodePath object is used by the controllers to reference a node path in a safe way
	 */
	public static class NodePath {

		/**
		 * The nodepath (e.g. standard/home)
		 */
		private String nodePath;
		
		/**
		 * Constructor. Initializes the nodePath by converting ~ to /
		 * @param dirtyNodePath		The unvalidated node path, e.g. standard~home
		 */
		public NodePath(String dirtyNodePath) {
			if (dirtyNodePath == null) {
				throw new IllegalArgumentException("Node Path cannot be null");
			}
			if (dirtyNodePath != null) {
				nodePath = dirtyNodePath.replaceAll("[~]", "/");
			}
		}

		/**
		 * Returns the value of the node path after the validations and transformations
		 * @return		The nodepath that we want to work with
		 */
		public String get() {
			return nodePath;
		}
	}

	/**
	 * Cache for the empty node path as a performance optimization
	 */
	private static final NodePath emptyNodePath = new NodePath("");
	
	/**
	 * Implementation of the converter. Transforms a node path that is in the format of standard~home to standard/home
	 */
    @Override
    public NodePath convert(final String nodePath) {
    	if (nodePath == null) {
    		return null;
    	}
        if (ASStringUtilities.isNullOrEmpty(nodePath)) {
            return emptyNodePath;
        }
        return new NodePath(nodePath);
    }
}

3) Lo usamos en el controller original:

	public ContentSlot deleteSlot( 
	        @RequestParam(value = "format", required = false) final String format, 
	        @RequestParam(value = "geo", required = false) final String geo, 
	        @RequestParam(value = "segment", required = false) final String segment, 
	        @RequestParam(value = "version", required = false) final String version, 
	        @PathVariable final String slot, 
	        @PathVariable final NodePath nodePath, 
	        HttpServletRequest request, 
	        Principal principal 
	                    ) { 
                String value = nodePath.get();
        }

Cheers,
Goose

Algunas veces es agradable ver un segmento de código sólo por si mismo, sin que realmente este dedicado a resolver ningún problema específico… simplemente se percibe una sensación agradable al verlo. Este es un ejemplito de ello:

@UiParamConverter
public class VersionConverter implements GenericConverter {

    @Target({ ElementType.PARAMETER, ElementType.FIELD })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface VersionCode {

    }

    @Target({ ElementType.PARAMETER, ElementType.FIELD })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface VersionId {

    }

    @Autowired(required = false)
    private VersionService versionService;

    private static final Logger log = LoggerFactory.getLogger(VersionConverter.class);

    @Override
    public Set getConvertibleTypes() {
        return Collections.singleton(new ConvertiblePair(String.class, PublishingVersion.class));
    }

    @Override
    public Object convert(final Object source, final TypeDescriptor sourceType, final TypeDescriptor targetType) {
        if ((!(source instanceof String)) || StringUtils.isBlank((String) source)) {
            return null;
        }

        final String fieldValue = (String) source;
        String fieldType = null;

        PublishingVersion version = null;

        try {
            // see what Version type annotation is on the TargetType
            if (targetType.hasAnnotation(VersionId.class)) {
                fieldType = new String("versionId");
                version = versionService.getVersionById(fieldValue);
            } else if (targetType.hasAnnotation(VersionCode.class)) {
                fieldType = new String("code");
                version = versionService.getVersionByCode(fieldValue);
            } else {
                // Default - assume we're dealing with a code
                fieldType = new String("code");
                version = versionService.getVersionByCode(fieldValue);
            }
        } catch (final Exception e) {
            log.warn(String.format("Error fetching version using field '%s' and value '%s'.", fieldType, fieldValue), e);
        }

        if (version == null) {
            log.info(String.format("Could not find version using field '%s' and value '%s'.", fieldType, fieldValue));
        }
        return version;
    }
}

Cheers,
Goose

A veces es útil NO tener que crear una clase que implemente una interfaz y luego hacer una instancia de esa clase.
En esos casos es posible simplemente crear la interfaz y hacer una instanciacion “directa”… mas o memos así:

Crear una interfaz así:

public interface ServerVars {
    String getAppName();
    Map getVars();
}

y ahora crear una instancia de “ese tipo” así:

    public ServerVars serverVars() {
        return new ServerVars() {
            @Override
            public String getAppName() {
                return "nemo";
            }
            @Override
            public Map getVars() {
                return Collections.emptyMap();
            }};
    }

Listo !!!

Cheers,
Goose

© 2017 Goose Workshop Suffusion theme by Sayontan Sinha