I would like to load Freemarker templates from one or more URLs so I subclassed the URLTemplate loader class and overrode the getURL(..) method to return the target URL (see below). I then added a couple of instances of this class to a multi template loader and added that to the Freemarker config. This works fine when the first URL returns a template but when it doesn't none of the other template loaders are called. What have I done wrong? I'm using v2.3 of Freemarker via the Restlet framework.
: : : : : : : : : :
TemplateLoader[] loaders = new TemplateLoader[] {
new MyTemplateLoader(new URL(request.getRootRef() + app.getRoot())),
new MyTemplateLoader(new URL(request.getRootRef() + "/"))
};
freemarkerConfig.setTemplateLoader(new MultiTemplateLoader(loaders));
: : : : : : : : : :
public class MyTemplateLoader extends URLTemplateLoader {
private URL root;
public MyTemplateLoader(URL root) {
super();
this.root = root;
}
#Override
protected URL getURL(String template) {
try {
URL tu = new URL(root, "./" + template);
return tu;
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}
}
A template is considered to be missing if TemplateLoader.findTemplateSource returns null for it. If it returns a non-null object, then MultiTemplateLoader assumes that it has found the template. In the case of URLTemplateLoader, findTemplateSource just returns what getURL does. So you have to check if the target exists, and then return null as URL if it doesn't. This works well for ClassTemplateLoader because getResource returns null URL for missing resources. But in general (if you don't know what kind of URL do you have) you will have to open an URLConnection and then connect() to see if the target exists. Or at least I guess that most URLSrteamHandler-s will check if the target exists at that point.
Related
{
"_embedded" : {
"patient" : {
"firstName" : "Kidus",
"_links" : {
"self" : {
"href" : "http://localhost:8090/api/patients/2{?projection}",
"templated" : true
},
}
}
As you can see i have an embedded entity(patient). It returns all the data including link to the entity but the link is templated. I'm not using a front-end HATEOAS client and i don't plan on changing course on that. So i would need a normal non-templated link. Is there any non-hacky way to achieve this?
You can force the template to be expanded in this way:
#GetMapping("/myresources/{id}")
public EntityModel<MyResource> myResource(String id) {
MyResource resource = ...;
return new EntityModel<>(
resource,
linkTo(methodOn(getClass()).myResource(id)).withSelfRel().expand(id));
}
You can modify the self link by using a custom RepresentationModelProcessor:
#Component
public class MyProjectionProcessor implements RepresentationModelProcessor<EntityModel<MyProjection>> {
private static final Pattern TEMPLATE_PATTERN = Pattern.compile("\\{\\?.*");
#Override
public EntityModel<MyProjection> process(final EntityModel<MyProjection> model) {
// copy the model without links
final EntityModel<MyProjection> newModel = EntityModel.of(model.getContent());
// loop over links
for (final Link link : model.getLinks()) {
Link newLink = link;
// replace self link if it is templated
if (link.hasRel(IanaLinkRelations.SELF) && link.isTemplated()) {
final String href = TEMPLATE_PATTERN.matcher(link.getHref()).replaceFirst("");
newLink = Link.of(href, IanaLinkRelations.SELF);
}
newModel.add(newLink);
}
return newModel;
}
}
I am using deeplinking in my app and Im looking to preset some parameters when navigating to the viewmodel using a IMvxNavigationFacade. The deep link url is like this:
myappname://deeplink/toviewwithdata/?navigatetoview=viewtype1&id=78910
So the deep linking is working and im getting to the navigation facade using the assembly attribute
[assembly: MvxNavigation(typeof(RoutingFacade), #"myappname://deeplink/toviewwithdata/\?navigatetoview=(?<viewtype>viewtype1)&id=(?<id>\d{5})")]
I tried to add other parameters to the MvxViewModelRequest using a MvxBundle but dont think im doing it right. here is my navigation facade:
public class RoutingFacade : IMvxNavigationFacade
{
public Task<MvxViewModelRequest> BuildViewModelRequest(string url, IDictionary<string, string> currentParameters)
{
var viewModelType = typeof(FirstViewModel);
var parameters = new MvxBundle();
try
{
// TODO: Update this to handle different view types and add error handling
if (currentParameters != null)
{
Debug.WriteLine($"RoutingFacade - {currentParameters["viewtype"]}, {currentParameters["id"]}");
switch (currentParameters["viewtype"])
{
case "viewtype1":
viewModelType = typeof(FirstViewModel);
parameters.Data.Add("test", "somevalue");
break;
default:
case "viewtype2":
viewModelType = typeof(FirstViewModel);
break;
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"RoutingFacade - Exception: {ex.Message}");
//TODO viewModelType = typeof(ErrorViewModel);
}
return Task.FromResult(new MvxViewModelRequest(viewModelType, parameters, null));
}
then my viewmodel Init method
public void Init(string id, string viewtype, string test)
{
// Do stuff with parameters
}
but the test parameter is null? How do you pass parameters into a MvxViewModelRequest?
Update:
Don’t know if its possible from looking at the source here https://github.com/MvvmCross/MvvmCross/blob/f4b2a7241054ac288a391c4c7b7a7342852e1e19/MvvmCross/Core/Core/Navigation/MvxNavigationService.cs#L122 as the request parameters get set from the regex of the deeplink url and the return from BuildViewModelRequest, facadeRequest.parameterValues get ignored.
Added this functionality in this pull request
I followed this tutorial, and created this code:
using Glass.Sitecore.Mapper;
using Sitecore.Mvc.Controllers;
using Sitecore.SecurityModel;
using SitecoreCMSMVCBase.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace SitecoreCMSMVCBase.Controllers
{
public class CommentController : SitecoreController
{
ISitecoreContext _context;
ISitecoreService _master;
public CommentController()
: this(
new SitecoreContext(),
new SitecoreService("master"))
{
}
/// <summary>
/// This constructor can be used with dependency injection or unit testing
/// </summary>
public CommentController(ISitecoreContext context, ISitecoreService master)
{
_context = context;
_master = master;
}
[HttpGet]
public override ActionResult Index()
{
var model = _context.GetCurrentItem<CommentPage>();
return View(model);
}
[HttpPost]
public ActionResult Index(Comment comment)
{
var webModel = _context.GetCurrentItem<CommentPage>();
if (ModelState.IsValid)
{
var masterModel = _master.GetItem<CommentPage>(webModel.Id);
if (masterModel.CommentFolder == null)
{
CommentFolder folder = new CommentFolder();
folder.Name = "Comments";
using (new SecurityDisabler())
{
_context.Create(masterModel, folder);
}
masterModel.CommentFolder = folder;
}
using (new SecurityDisabler())
{
comment.Name = DateTime.Now.ToString("yyyyMMddhhmmss");
//create the comment in the master database
_master.Create(masterModel.CommentFolder, comment);
webModel.CommentAdded = true;
}
}
return View(webModel);
}
}
}
Models are identical with tutorial, so I will not paste them.
My route configuration looks like this:
routes.MapRoute(
"CommentController", // Route name
"Comment/{action}/{id}", // URL with parameters
new { controller = "Comment", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
When I navigate to /comment I see this exception:
Glass.Sitecore.Mapper.MapperException: Context has not been loaded
I tried with commenting my route specification (as there was nothing about routes in tutorial), and then error is different (throwing by Sitecore CMS itself):
The requested document was not found
Do you know how to load Sitecore context into custom Controller, and make this simple example work? I was looking everywhere but couldn't find any good answer...
I think this is more a Glass setup issue, rather than an MVC routing problem.
To setup Glass, you need to initialise the context in your application start method in your Global.asax file.
var loader = new Glass.Sitecore.Mapper.Configuration.Attributes.AttributeConfigurationLoader(
"Glass.Sitecore.Mapper.Tutorial.Models, Glass.Sitecore.Mapper.Tutorial");
Glass.Sitecore.Mapper.Context context = new Context(loader);
For other Glass-setup related stuff I recommend following the first tutorial on the glass.lu website.
http://www.glass.lu/tutorials/glass-sitecore-mapper-tutorials/tutorial-1-setup/
This method doesn't need Glass at all!
First step is to set your route in Global.asax file.
routes.MapRoute(
"DemoController", // Route name
"Demo/{action}/{param}", // URL with parameters
new { controller = "Demo", action = "Index", param = "", scItemPath = "/sitecore/content/DemoHomePage" } // Parameter defaults
);
Notice that controller is not taken as parameter, but is fixed, to prevent handling it by Sitecore. More info here and here. Notice that there is one additional parameter - scItemPath. It contains path to item which by default will be included in page context.
Having this route our traffic from /demo is handled by DemoController and Index action. Inside this action all you need is to add is this line:
Sitecore.Data.Items.Item item = Sitecore.Mvc.Presentation.PageContext.Current.Item;
item variable will contain your Sitecore item pointed by scItemPath.
And that's all - it should work well now - hope it helps!
I have a ASP.NET Web API (.NET 4) application which has a few controllers. We will run several instances of the Web API application on IIS with one difference. Only certain controllers will be available under certain IIS instances. What I was thinking is to disable/unload the controllers that are not applicable to an instance when the instance starts up.
Anyone got some information that could guide me in the right direction on this?
You can put your own custom IHttpControllerActivator in by decorating the DefaultHttpControllerActivator. Inside just check for a setting and only create the controller if allowed.
When you return null from the Create method the user will receive 404 Not Found message.
My example shows a value in App Settings (App.Config or Web.Config) being checked but obviously this could any other environment aware condition.
public class YourCustomControllerActivator : IHttpControllerActivator
{
private readonly IHttpControllerActivator _default = new DefaultHttpControllerActivator();
public YourCustomControllerActivator()
{
}
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
if (ConfigurationManager.AppSettings["MySetting"] == "Off")
{
//Or get clever and look for attributes on the controller in controllerDescriptor.GetCustomAttributes<>();
//Or use the contoller name controllerDescriptor.ControllerName
//This example uses the type
if (controllerType == typeof (MyController) ||
controllerType == typeof (EtcController))
{
return null;
}
}
return _default.Create(request, controllerDescriptor, controllerType);
}
}
You can switch your activator in like so:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new YourCustomControllerActivator());
Update
It has been a while since I looked at this question but if I was to tackle it today I would alter the approach slightly and use a custom IHttpControllerSelector. This is called before the activator and makes for a slightly more efficient place to enable and disable controllers... (although the other approach does work). You should be able to decorate or inherit from DefaultHttpControllerSelector.
Rather than unloading the controllers, I think I'd create a custom Authorize attribute that looked at the instance information in deciding to grant authorization.
You would add the following to each controller at the class level, or you could also add this to individual controller actions:
[ControllerAuthorize (AuthorizedUserSources = new[] { "IISInstance1","IISInstance2","..." })]
Here's the code for the Attribute:
public class ControllerAuthorize : AuthorizeAttribute
{
public ControllerAuthorize()
{
UnauthorizedAccessMessage = "You do not have the required access to view this content.";
}
//Property to allow array instead of single string.
private string[] _authorizedSources;
public string UnauthorizedAccessMessage { get; set; }
public string[] AuthorizedSources
{
get { return _authorizedSources ?? new string[0]; }
set { _authorizedSources = value; }
}
// return true if the IIS instance ID matches any of the AllowedSources.
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
//If no sources are supplied then return true, assuming none means any.
if (!AuthorizedSources.Any())
return true;
return AuthorizedSources.Any(ut => ut == httpContext.ApplicationInstance.Request.ServerVariables["INSTANCE_ID"]);
}
The IHttpControllerActivator implementation doesn't disable the routes defined using attribute routing , if you want to switch on/off a controller and have a default catch all route controller. Switching off using IHttpControllerActivator disables the controller but when the route is requested it doesn't hit the catch all route controller -it simply tries to hit the controller that was removed and returns no controller registered.
I'm trying to use the latest spring security plugin for grails, but I've hit a little bump.
I have a controller with this method:
#Secured(['ROLE_USER'])
def query = {
}
When I hit http://localhost:8080/myApp/myController/query, I get prompted for authorization as appropriate. However, I need to do content type negotiation via the filename extension. Using
grails.mime.file.extensions=true
I can use the same UrlMappings and get to my controller method via .../myApp/myController/query.js?params=blah. However, I am not prompted for authentication, and either the request goes through automatically or fails, depending on how I've set grails.plugins.springsecurity.rejectIfNoRule
How can I use file type negotiation with the spring security plugin?
Turn off grails.mime.file.extensions and add this filter:
class FileExtensionContentNegotiationFilters {
final static String DEFAULT_FORMAT = "js"
def filters = {
all(controller: '*', action: '*') {
before = {
addFormatToRequestByFileExtension(request)
}
after = {
}
afterView = {
}
}
}
protected addFormatToRequestByFileExtension(def request) {
String suffix = getSuffixFromPath(request.forwardURI)
String extension = FilenameUtils.getExtension(suffix)
if (extension.isEmpty()) {
request[GrailsApplicationAttributes.CONTENT_FORMAT] = DEFAULT_FORMAT
}
else {
request[GrailsApplicationAttributes.CONTENT_FORMAT] = extension
}
}
protected String getSuffixFromPath(String pathWithoutParams) {
int lastSlash = pathWithoutParams.lastIndexOf("/")
if (lastSlash < 0) {
return ""
}
return pathWithoutParams.substring(lastSlash + 1)
}
}
The solution above does not work for me as expected. A 404 response is generated when I request an URL with extension.
I come with another solution that does not need to turn off grails.mime.file.extensions and does not need an extra Filter. Instead, it's a modification of the plugin's class org.codehaus.groovy.grails.plugins.springsecurity.AnnotationFilterInvocationDefinition. What you need to do is to edit the method determineUrl as shown below (look at the comments to identify the changes):
#Override
protected String determineUrl(final FilterInvocation filterInvocation) {
HttpServletRequest request = filterInvocation.getHttpRequest();
HttpServletResponse response = filterInvocation.getHttpResponse();
GrailsWebRequest existingRequest = WebUtils.retrieveGrailsWebRequest();
String requestUrl = request.getRequestURI().substring(request.getContextPath().length());
/** The following 2 lines were added */
int indexOfPeriod = requestUrl.indexOf('.');
String requestUrlForMatching = (indexOfPeriod != -1) ? requestUrl.substring(0, indexOfPeriod) : requestUrl;
String url = null;
try {
GrailsWebRequest grailsRequest = new GrailsWebRequest(request, response,
ServletContextHolder.getServletContext());
WebUtils.storeGrailsWebRequest(grailsRequest);
Map<String, Object> savedParams = copyParams(grailsRequest);
/* Use requestUrlForMatching instead of requestUrl */
for (UrlMappingInfo mapping : _urlMappingsHolder.matchAll(requestUrlForMatching)) {
configureMapping(mapping, grailsRequest, savedParams);
url = findGrailsUrl(mapping);
if (url != null) {
break;
}
}
}
finally {
if (existingRequest == null) {
WebUtils.clearGrailsWebRequest();
}
else {
WebUtils.storeGrailsWebRequest(existingRequest);
}
}
if (!StringUtils.hasLength(url)) {
// probably css/js/image
url = requestUrl;
}
return lowercaseAndStripQuerystring(url);
}
The problem is that URLs with extensions do not match any URL in UrlMappings using UrlMappingsHolder.matchAll method. So, the solution is to ommit the extension prior to look for matches. With this changes everything works as expected.
I also create a pull request with the fix available at https://github.com/grails-plugins/grails-spring-security-core/pull/24
You can see the changes at https://github.com/arcesino/grails-spring-security-core/commit/19f87168ec4422b4fe06cc6914adeb1bae4b8752
Tested with version 1.2.7.3