Mapping a URL to the most specific context root - java

i have the following situation:
I want to map incoming queries (i use a servlet filter to access the queries) to the suitable applications. For this, i have a table where i map the applications to their contextroots, e.g.:
/application1/ | Application1 Rootcontext
/application1/subcontext1 | Application1 Subcontext 1
/application1/subcontext2 | Application1 Subcontext 2
/application2/ | Application2
So when i have a query with the path /application1/subcontext1/someotherpath, i want to get Application1 Subcontext 1, when i have a query URL /application1/sompath, i want to get Application 1 Rootcontext.
My first guess was, that i build some sort of tree with my mappings of the contextroots (every part of that URL as a node), and then split up the query URL and walk down the tree to get the most specific application mapping.
Would that be the best solution, or do you have any other suggestions for my problem?

Instead of a tree and walking forwards you can have your map as a Map<String, ApplicationContext> and walk backwards until you find the first non-null fit. This code should give you a rough idea of how to do it:
import java.util.HashMap;
import java.util.Map;
public class Main {
public static final class ApplicationContext {
private final String app;
private final String ctx;
public ApplicationContext(final String app, final String ctx) {
this.app = app;
this.ctx = ctx;
}
#Override
public String toString() {
return "ApplicationContext[" + app + "/" + ctx + "]";
}
}
private static ApplicationContext ac(final String app, final String ctx) {
return new ApplicationContext(app, ctx);
}
private static ApplicationContext getApplicationContext(final String url,
final Map<String, ApplicationContext> urlMap) {
String specificUrl = url;
ApplicationContext result = null;
while (specificUrl != null && result == null) {
result = urlMap.get(specificUrl);
specificUrl = shortenUrl(specificUrl);
}
return result;
}
public static void main(final String[] args) throws Exception {
final Map<String, ApplicationContext> urlMap = new HashMap<String, ApplicationContext>();
urlMap.put("/application1", ac("Application1", "Root"));
urlMap.put("/application1/subcontext1", ac("Application1", "SubContext1"));
urlMap.put("/application1/subcontext2", ac("Application1", "SubContext2"));
urlMap.put("/application1/subcontext2/subcontext3", ac("Application1", "SubContext3"));
urlMap.put("/application2", ac("Application2", null));
System.out.println(getApplicationContext("/application1/", urlMap));
System.out.println(getApplicationContext("/application1/abc", urlMap));
System.out.println(getApplicationContext("/application1/subcontext2/abc", urlMap));
}
private static String shortenUrl(final String url) {
final int index = url.lastIndexOf('/');
if (index > 0) {
return url.substring(0, index);
}
else {
return null;
}
}
}
And a fiddle for it.

Related

Handling dead-letter queue message-broker independent way

I have a project that currently uses Spring Cloud Streams and RabbitMQ underneath. I've implemented a logic based on the documentation. See below:
#Component
public class ReRouteDlq {
private static final String ORIGINAL_QUEUE = "so8400in.so8400";
private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
private static final String X_RETRIES_HEADER = "x-retries";
private static final String X_ORIGINAL_EXCHANGE_HEADER = RepublishMessageRecoverer.X_ORIGINAL_EXCHANGE;
private static final String X_ORIGINAL_ROUTING_KEY_HEADER = RepublishMessageRecoverer.X_ORIGINAL_ROUTING_KEY;
#Autowired
private RabbitTemplate rabbitTemplate;
#RabbitListener(queues = DLQ)
public void rePublish(Message failedMessage) {
Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
if (retriesHeader == null) {
retriesHeader = Integer.valueOf(0);
}
if (retriesHeader < 3) {
headers.put(X_RETRIES_HEADER, retriesHeader + 1);
String exchange = (String) headers.get(X_ORIGINAL_EXCHANGE_HEADER);
String originalRoutingKey = (String) headers.get(X_ORIGINAL_ROUTING_KEY_HEADER);
this.rabbitTemplate.send(exchange, originalRoutingKey, failedMessage);
}
else {
this.rabbitTemplate.send(PARKING_LOT, failedMessage);
}
}
#Bean
public Queue parkingLot() {
return new Queue(PARKING_LOT);
}
}
It does what it is expected, however, it is binded to RabbitMQ, and my company is planning to stop using this message broker in one year or two (don't know why, must be some crazy business). So, I want to implement the same thing, but detach it from any message broker.
I tried changing the rePublish method this way, but it does not work:
#StreamListener(Sync.DLQ)
public void rePublish(Message failedMessage) {
Map<String, Object> headers = failedMessage.getHeaders();
Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
if (retriesHeader == null) {
retriesHeader = Integer.valueOf(0);
}
if (retriesHeader < 3) {
headers.put(X_RETRIES_HEADER, retriesHeader + 1);
String exchange = (String) headers.get(X_ORIGINAL_EXCHANGE_HEADER);
String originalRoutingKey = (String) headers.get(X_ORIGINAL_ROUTING_KEY_HEADER);
this.rabbitTemplate.send(exchange, originalRoutingKey, failedMessage);
}
else {
this.rabbitTemplate.send(PARKING_LOT, failedMessage);
}
}
It fails because the Message class has immutable Headers - throws exception on the put attempt saying you can't change its values (uses org.springframework.messaging.Message class).
Is there a way to implement this dead-letter queue handler in a message broker independent way?
Use
MessageBuilder.fromMessage(message)
.setHeader("foo", "bar")
...
.build();
Note that the message in #StreamListener is a spring-messaging Message<?>, not a spring-amqp Message and can't be sent using the template that way; you need an output binding to send the message to.

Get Server error when accessing an address, but no stack trace

I have a hybris website with a single mapping. Each time I try to access a particular url:
Is there any way to debug such an error?
This is the controller of the page:
#Controller
#RequestMapping(value = "/cart")
public class CartPageController extends AbstractPageController
{
private static final String CART_CMS_PAGE = "cartPage";
private static final Integer DEFAULT_COOKIE_EXPIRY_AGE = 5184000;
private static final String DEFAULT_CART_IDENTIFIER_COOKIE_NAME = "cart.identifier.cookie.name";
private static final Logger LOG = Logger.getLogger(CartPageController.class);
#Resource(name = "cartFacade")
private CartFacade cartFacade;
#Resource(name = "userService")
private UserService userService;
#Resource(name = "baseStoreService")
private BaseStoreService baseStoreService;
#Resource(name = "catalogVersionService")
private CatalogVersionService catalogVersionService;
#RequestMapping(method = RequestMethod.GET)
public String showCart(HttpServletRequest request, HttpServletResponse response, final Model model)
throws CMSItemNotFoundException
{
CartData cartData = cartFacade.getSessionCartWithEntryOrdering(true);
final String cartCookieIdentifier = getCartCookieIdentifier();
if (!cartFacade.hasEntries())
{
final String cartId = getCookieValue(request, cartCookieIdentifier);
final Optional<CartData> cartDataOptional = cartFacade.getCartsForCurrentUser().stream()
.filter(c -> c.getCode().equals(cartId)).findFirst();
if (cartDataOptional.isPresent())
{
cartData = cartDataOptional.get();
}
}
setCookieValue(response, cartCookieIdentifier, cartData.getCode());
model.addAttribute("cart", cartData);
setupPageModel(model);
String model1 = getViewForPage(model);
return model1;
}
protected void setupPageModel(Model model) throws CMSItemNotFoundException
{
storeCmsPageInModel(model, getContentPageForLabelOrId(CART_CMS_PAGE));
setUpMetaDataForContentPage(model, getContentPageForLabelOrId(CART_CMS_PAGE));
}
protected String getCookieValue(final HttpServletRequest request, final String cookieName)
{
return Arrays.stream(request.getCookies())
.filter(c -> c.getName().equals(cookieName))
.findFirst()
.map(Cookie::getValue)
.orElse(null);
}
protected void setCookieValue(final HttpServletResponse response, final String cookieName, final String cookieValue)
{
final Cookie cookie = new Cookie(cookieName, cookieValue);
cookie.setMaxAge(DEFAULT_COOKIE_EXPIRY_AGE);
response.addCookie(cookie);
}
protected String getCartCookieIdentifier()
{
final String baseStoreId = getCurrentBaseStoreId();
final String catalogId = getCurrentProductCatalogId();
if (StringUtils.isNotEmpty(baseStoreId) && StringUtils.isNotEmpty(catalogId))
{
return baseStoreId + "-" + catalogId;
}
return DEFAULT_CART_IDENTIFIER_COOKIE_NAME;
}
protected String getCurrentBaseStoreId()
{
final BaseStoreModel baseStore = baseStoreService.getCurrentBaseStore();
return baseStore == null ? StringUtils.EMPTY : baseStore.getUid();
}
protected String getCurrentProductCatalogId()
{
for (final CatalogVersionModel catalogVersionModel : catalogVersionService.getSessionCatalogVersions())
{
if (!((catalogVersionModel.getCatalog() instanceof ContentCatalogModel) || (catalogVersionModel
.getCatalog() instanceof ClassificationSystemModel)))
{
return catalogVersionModel.getCatalog().getId();
}
}
return StringUtils.EMPTY;
}
}
The content of the jsp page is not really that important since it can be empty and this behaviour still happens. I do not know exactly what could be the root of this. Is there any effective way to debug such issues?
This is a usual bug when creating B2B Sites. A workaround is to open the /smartedit and login to your site from there. Hybris will create a proper session and you should be able to open the site.
Possible long time solution:
If you are creating a B2B site, check spring-filter-config.xml in your Storefront extension and check this section. It should look like this:
<alias name="b2cAcceleratorSiteChannels" alias="acceleratorSiteChannels"/>
<util:set id="b2cAcceleratorSiteChannels" value-type="de.hybris.platform.commerceservices.enums.SiteChannel">
<ref bean="SiteChannel.B2C"/>
<ref bean="SiteChannel.B2B"/>
</util:set>
You can remove the SiteChannel.B2C if everything is fine
It looks like this behaviour was due to a bad argument when importing an impex related to this page:
INSERT_UPDATE PageTemplate;$contentCV[unique = true];uid[unique = true];name;frontendTemplateName;restrictedPageTypes(code);active[default = true]
;;CartPageTemplate;Cart Page Template;"cartPage";ContentPage;false;
should've been
INSERT_UPDATE PageTemplate;$contentCV[unique = true];uid[unique = true];name;frontendTemplateName;restrictedPageTypes(code);active[default = true]
;;CartPageTemplate;Cart Page Template;"cart/cartPage";ContentPage;false;
Replacing "cartPage" with "cart/cartPage" did all the magic.

Getting parameters from ExecutionContext

I have a spring batch job that is executed with web parameters like this:
https://localhost:8443/batch/async/orz003A?id=123&name=test
I've added these parameters, id and test to my ExecutionContext
I'm having trouble accessing them in my Setup Tasklet, seen below.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;
import com.yrc.mcc.app.online.NTfp211;
import com.yrc.mcc.core.batch.tasklet.AbstractSetupTasklet;
#Component
public class Tfp211SetupTasklet extends AbstractSetupTasklet {
final static Logger LOGGER = LoggerFactory.getLogger(Tfp211SetupTasklet.class);
String gapId;
#Override
protected RepeatStatus performTask(ExecutionContext ec) {
//TODO create the map, check the params for the needed params
// throw an error if the param doesn't exist, because the param
// is necessary to run the job. If the param does exist, set the specific param
if (ec.isEmpty()) {
LOGGER.info("this shit is empty");
}
//setg on GAPID
gapId = ec.toString();
ec.get(BATCH_PROGRAM_PARAMS);
LOGGER.info(gapId);
ec.put(AbstractSetupTasklet.BATCH_PROGRAM_NAME, NTfp211.class.getSimpleName());
return RepeatStatus.FINISHED;
}
}
Any suggestions?
edit:
Here is a piece from my AbstractSetupTaskler
Map<String, String> params = new HashMap<>();
if (!ec.containsKey(BATCH_PROGRAM_PARAMS)) {
ec.put(BATCH_PROGRAM_PARAMS, params);
}
within each job's SetupTasklet I want to specify the parameters needed for that job
edit: I have this tasklet that I believe launches my jobs
#Component
public class CallM204ProgramTasklet implements Tasklet {
private static final Logger LOGGER = LoggerFactory.getLogger(CallM204ProgramTasklet.class);
#Autowired
private CommonConfig commonConfig;
#Autowired
private ProgramFactory programFactory;
#Autowired
private MidusService midusService;
#Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
ExecutionContext ec = chunkContext.getStepContext().getStepExecution().getJobExecution().getExecutionContext();
JobParameters jobParameters = chunkContext.getStepContext().getStepExecution().getJobParameters();
jobParameters.getParameters();
String progName = ec.getString(AbstractSetupTasklet.BATCH_PROGRAM_NAME);
Random randomSession = new Random();
String sessionId = "000000" + randomSession.nextInt(1000000);
sessionId = sessionId.substring(sessionId.length() - 6);
SessionData sessionData = new SessionDataImpl("Batch_" + sessionId, commonConfig);
IOHarness io = new BatchIOHarnessImpl(midusService, commonConfig.getMidus().getSendToMidus());
sessionData.setIOHarness(io);
sessionData.setUserId("mccBatch");
Program program = programFactory.createProgram(progName, sessionData);
String progResult = null;
// Create necessary globals for flat file handling.
#SuppressWarnings("unchecked")
Map<String, MccFtpFile> files = (Map<String, MccFtpFile>) ec.get(AbstractSetupTasklet.BATCH_FTP_FILES);
if (files != null) {
for (MccFtpFile mccFtpFile : files.values()) {
program.setg(mccFtpFile.getGlobalName(), mccFtpFile.getLocalFile());
}
}
#SuppressWarnings("unchecked")
Map<String, String> params = (Map<String, String>) ec.get(AbstractSetupTasklet.BATCH_PROGRAM_PARAMS);
//put params into globals
if (params != null) {
params.forEach((k, v) -> program.setg(k, v));
}
try {
program.processUnthreaded(sessionData);
progResult = io.close(sessionData);
} catch (Exception e) {
progResult = "Error running renovated program " + progName + ": " + e.getMessage();
LOGGER.error(progResult, e);
chunkContext.getStepContext().getStepExecution().setExitStatus(ExitStatus.FAILED);
} finally {
String currResult = ec.getString(AbstractSetupTasklet.BATCH_PROGRAM_RESULT).trim();
// Put the program result into the execution context.
ec.putString(AbstractSetupTasklet.BATCH_PROGRAM_RESULT, currResult + "\r" + progResult);
}
return RepeatStatus.FINISHED;
}
}
You need to setup a job launcher and pass the parameters as described in the docs here: https://docs.spring.io/spring-batch/4.0.x/reference/html/job.html#runningJobsFromWebContainer.
After that, you can get access to job parameters in your tasklet from the chunk context. For example:
class MyTasklet implements Tasklet {
#Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
JobParameters jobParameters = chunkContext.getStepContext().getStepExecution().getJobParameters();
// get id and name from jobParameters
// use id and name to do the required work
return RepeatStatus.FINISHED;
}
}

Java file encoding magic

Strange thing happened in Java Kingdom...
Long story short: I use Java API V3 to connect to QuickBooks and fetch the data form there (services for example).
Everything goes fine except the case when a service contains russian symbols (or probably non-latin symbols).
Here is Java code that does it (I know it's far from perfect)
package com.mde.test;
import static com.intuit.ipp.query.GenerateQuery.$;
import static com.intuit.ipp.query.GenerateQuery.select;
import java.util.LinkedList;
import java.util.List;
import com.intuit.ipp.core.Context;
import com.intuit.ipp.core.ServiceType;
import com.intuit.ipp.data.Item;
import com.intuit.ipp.exception.FMSException;
import com.intuit.ipp.query.GenerateQuery;
import com.intuit.ipp.security.OAuthAuthorizer;
import com.intuit.ipp.services.DataService;
import com.intuit.ipp.util.Config;
public class TestEncoding {
public static final String QBO_BASE_URL_SANDBOX = "https://sandbox-quickbooks.api.intuit.com/v3/company";
private static String consumerKey = "consumerkeycode";
private static String consumerSecret = "consumersecretcode";
private static String accessToken = "accesstokencode";
private static String accessTokenSecret = "accesstokensecretcode";
private static String appToken = "apptokencode";
private static String companyId = "companyidcode";
private static OAuthAuthorizer oauth = new OAuthAuthorizer(consumerKey, consumerSecret, accessToken, accessTokenSecret);
private static final int PAGING_STEP = 500;
public static void main(String[] args) throws FMSException {
List<Item> res = findAllServices(getDataService());
System.out.println(res.get(1).getName());
}
public static List<Item> findAllServices(DataService service) throws FMSException {
Item item = GenerateQuery.createQueryEntity(Item.class);
List<Item> res = new LinkedList<>();
for (int skip = 0; ; skip += PAGING_STEP) {
String query = select($(item)).skip(skip).take(PAGING_STEP).generate();
List<Item> items = (List<Item>)service.executeQuery(query).getEntities();
if (items.size() > 0)
res.addAll(items);
else
break;
}
System.out.println("All services fetched");
return res;
}
public static DataService getDataService() throws FMSException {
Context context = getContext();
if (context == null) {
System.out.println("Context is null, something wrong, dataService also will null.");
return null;
}
return getDataService(context);
}
private static Context getContext() {
try {
return new Context(oauth, appToken, ServiceType.QBO, companyId);
} catch (FMSException e) {
System.out.println("Context is not loaded");
return null;
}
}
protected static DataService getDataService(Context context) throws FMSException {
DataService service = new DataService(context);
Config.setProperty(Config.BASE_URL_QBO, QBO_BASE_URL_SANDBOX);
return new DataService(context);
}
}
This file is saved in UTF-8. And it prints something like
All services fetched
Сэрвыс, отнюдь
But! When I save this file in UTF-8 with BOM.... I get the correct data!
All services fetched
Сэрвыс, отнюдь
Does anybody can explain what is happening? :)
// I use Eclipse to run the code
You are fetching data from a system that doesn't share the same byte ordering as you, so when you save the file with BOM, it adds enough information in the file that future programs will read it in the remote system's byte ordering.
When you save it without BOM, it wrote the file in the remote system's byte ordering without any indication of the stored byte order, so when you read it you read it with the local system's (different) byte order. This jumbles up the bytes within the multi-byte characters, making the output appear as nonsense.

URI template needs to match with variable value that is a set of folders

I am using org.springframework.web.util.UriTemplate and I am trying to match this uri template:
http://{varName1}/path1/path2/{varName2}/{varName3}/{varName4}
with the following uri:
http://hostname/path1/path2/design/99999/product/schema/75016TC806AA/TC806AA.tar
Currently I get the following uri variables:
{varName1=hostname, varName2=design/99999/product/schema, varName3=75016TC806AA,varName4=TC806AA.tar}
But I would like to get the following uri variables:
{varName1=hostname, varName2=design varName3=99999, varName4=product/schema/75016TC806AA/TC806AA.tar}
I tried to use wildcards as * or + in my template, but that doesn't seems to work:
http://{varName1}/path1/path2/{varName2}/{varName3}/{varName4*}
http://{varName1}/path1/path2/{varName2}/{varName3}/{+varName4}
Edited
String url = http://localhost/path1/path2/folder1/folder2/folder3/folder4/folder5
UriTemplate uriTemplate = new UriTemplate(urlTemplateToMatch);
Map<String, String> uriVariables = uriTemplate.match(url);
String urlTemplateToMatch1 = http://{varName1}/path1/path2/{varName2}/{varName3}/{varName4}
uriVariables1 = {varName1=localhost, varName2=folder1/folder2/folder3, varName3=folder4, varName4=folder5}
String urlTemplateToMatch2 = http://{varName1}/test1/test2/{varName2:.*?}/{varName3:.*?}/{varName4}
uriVariables2 = {varName1=localhost, varName2:.*?=folder1/folder2/folder3, varName3:.*?=folder4, varName4=folder5}
String urlTemplateToMatch3 = http://{varName1}/test1/test2/{varName2:\\w*}/{varName3:.\\w*}/{varName4}
uriVariables3 = {varName1=localhost, varName2:\w*=folder1/folder2/folder3, varName3:\w*=folder4, varName4=folder5}
Try with:
http://{varName1}/path1/path2/{varName2:.*?}/{varName3:.*?}/{varName4}
or may be
http://{varName1}/path1/path2/{varName2:\\w*}/{varName3:\\w*}/{varName4}
Edit
#RunWith(BlockJUnit4ClassRunner.class)
public class UriTemplateTest {
private String URI = "http://hostname/path1/path2/design/99999/product/schema/75016TC806AA/TC806AA.tar";
private String TEMPLATE_WORD = "http://{varName1}/path1/path2/{varName2:\\w*}/{varName3:\\w*}/{varName4}";
private String TEMPLATE_RELUCTANT = "http://{varName1}/path1/path2/{varName2:.*?}/{varName3:.*?}/{varName4}";
private Map<String, String> expected;
#Before
public void init() {
expected = new HashMap<String, String>();
expected.put("varName1", "hostname");
expected.put("varName2", "design");
expected.put("varName3", "99999");
expected.put("varName4", "product/schema/75016TC806AA/TC806AA.tar");
}
#Test
public void testTemplateWord() {
testTemplate(TEMPLATE_WORD);
}
#Test
public void testTemplateReluctant() {
testTemplate(TEMPLATE_RELUCTANT);
}
private void testTemplate(String template) {
UriTemplate ut = new UriTemplate(template);
Map<String, String> map = ut.match(URI);
Assert.assertEquals(expected, map);
}
}

Categories