When I try to deploy my Cloud Endpoints Framework api using gcloud service-management deploy openapi.json, I get many errors similar to:
ERROR: openapi.json: Operation 'get' in path '/sd/v1/groups/{id}': operationId 'SdGet' has duplicate entry
Inspecting the generated openapi.json document, I see it has many duplicated operationIds. For example notice both of these use SdGet:
{
...
"paths": {
"/sd/v1/feeds/{id}": {
"get": {
"operationId": "SdGet",
...
}
},
"/sd/v1/groups/{id}": {
"get": {
"operationId": "SdGet",
...
}
}
}
}
My backend is in Java. I have a multiclass API using inheritance, which seems to confrm to the recommendations in the docs. Here are the relevant parts for this example:
#Api(name = "sd", ...)
public class Endpoints { ... }
public class FeedEndpoints extends Endpoints {
#ApiMethod(
path = "feeds/{id}",
name = "feeds.get",
httpMethod = HttpMethod.GET)
public Feed get(...) { ... }
...
}
public class GroupEndpoints extends Endpoints {
#ApiMethod(
path = "groups/{id}",
name = "groups.get",
httpMethod = HttpMethod.GET)
public Group get(...) { ... }
...
}
To generate openapi.json I modeled my config after Google's getting started guide. So in pom.xml I have something like this, which lets me generate it with the command mvn exec:java -DGetSwaggerDoc:
<profiles>
<profile>
<id>GetSwaggerDoc</id>
...
<build>
<plugins>
<plugin>
...
<configuration>
...
<arguments>
<argument>get-swagger-doc</argument>
<argument>--hostname=echo-api.endpoints.${endpoints.project.id}.cloud.goog</argument>
<argument>--war=target/blah-1.0-SNAPSHOT</argument>
<argument>blah.FeedEndpoints</argument>
<argument>blah.GroupEndpoints</argument>
...
</arguments>
</configuration>
...
</plugin>
</plugins>
</build>
</profile>
</profiles>
What am I doing wrong? How can I define things differently so that the generated api specification does not use duplicate ids?
#saiyr informs me this is a bug in the framework (see comments on the question), so I filed a report here. For now I worked around it by renaming the API methods in all my endpoint classes to be unique, like this:
#Api(...)
public class Endpoints { ... }
public class FeedEndpoints extends Endpoints {
#ApiMethod(...)
public Feed getFeed(...) { ... }
...
}
public class GroupEndpoints extends Endpoints {
#ApiMethod(...)
public Group getGroup(...) { ... }
...
}
Related
We have a DNN website with custom modules which uses Ajax to load Grid Items.
How can I get DNN to create a sitemap for these links as well? I am currently using an external program but would like DNN to generate these sitemaps automatically with all our links.
The site is: https://www.parrot.co.za
You would typically do this by creating a SiteMap provider for your module.
You can find a working example in my DNNSimpleArticle module on GitHub
public class Sitemap : SitemapProvider
{
public override List<SitemapUrl> GetUrls(int portalId, PortalSettings ps, string version)
{
var listOfUrls = new List<SitemapUrl>();
foreach (Article ai in ArticleController.GetAllArticles(portalId))
{
var pageUrl = new SitemapUrl
{
Url =
ArticleController.GetArticleLink(ai.TabID, ai.ArticleId),
Priority = (float)0.5,
LastModified = ai.LastModifiedOnDate,
ChangeFrequency = SitemapChangeFrequency.Daily
};
listOfUrls.Add(pageUrl);
}
return listOfUrls;
}
}
and then you need to register the sitemap with DNN in the .DNN file used during the module's installation
<component type="Config">
<config>
<configFile>web.config</configFile>
<install>
<configuration>
<nodes>
<node path="/configuration/dotnetnuke/sitemap/providers" action="update" key="name" collision="overwrite">
<add name="DNNSimpleArticleSiteMapProvider" type="Christoc.Modules.dnnsimplearticle.Providers.Sitemap.Sitemap, DNNSimpleArticle" providerPath="~\DesktopModules\dnnsimplearticle\Providers\Sitemap\" />
</node>
</nodes>
</configuration>
</install>
<uninstall>
<configuration>
<nodes />
</configuration>
</uninstall>
</config>
</component>
I was looking into Camel-Scr and in pom.xml i saw
<artifactId>camel-scr</artifactId>
<name>Camel :: SCR (deprecated)</name>
<description>Camel with OSGi SCR (Declarative Services)</description>
Why this is deprecated? what alternative would community use in future?
My guess is that it was simply too complex with all the annotations and properties and hence probably didn't get much use compared to much simpler OSGi blueprints.
Usage of Apache Camel with Declarative services or SCR is pretty straightforward with the help of OsgiDefaultCamelContext. You can create the context manually, add routes and configurations and register it to OSGi with bundleContext.registerService method.
Example:
package com.example;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.core.osgi.OsgiDefaultCamelContext;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Component(
immediate = true
)
public class OsgiDSCamelContextComponent {
private final static Logger LOGGER = LoggerFactory.getLogger(ExampleCamelContext.class);
CamelContext camelContext;
ServiceRegistration<CamelContext> camelContextRegistration;
#Activate
public void onActivate(BundleContext bundleContext, Map<String, ?> configs){
// Create new OsgiDefaultCamelContext with injected bundleContext
OsgiDefaultCamelContext newCamelContext = new OsgiDefaultCamelContext(bundleContext);
newCamelContext.setName("OsgiDSCamelContext");
// Add configs from com.example.OsgiDSCamelContextComponent.cfg
// available for use with property placeholders
Properties properties = new Properties();
properties.putAll(configs);
newCamelContext.getPropertiesComponent()
.setInitialProperties(properties);
camelContext = newCamelContext;
try {
// In Apache Camel 3.x CamelContext needs to be started before adding RouteBuilders.
camelContext.start();
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
from("timer:exampleTimer?period=3000")
.routeId("exampleTimer")
.log("Hello from Camel using Declarative services");
}
});
//Create dictionary holding properties for the CamelContext service.
Dictionary serviceProperties = new Hashtable<>();
serviceProperties.put("context.name", "OsgiDSCamelContext");
serviceProperties.put("some.property", "SomeValue");
// Register the new CamelContext instance as a service to Karaf with given properties
camelContextRegistration = bundleContext.registerService(CamelContext.class,
camelContext, serviceProperties);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
#Deactivate
public void onDeactivate(){
// Stop camel context when bundle is stopped
if(camelContext != null){
camelContext.stop();
}
// unregister camel context service when bundle is stopped
if(camelContextRegistration != null){
camelContextRegistration.unregister();
}
}
}
Now you could also use DS Service Components to register RouteBuilder service(s) and inject them to CamelContext using #Reference annotation and List<RouteBuilder>.
package com.example.routes;
import org.apache.camel.builder.RouteBuilder;
import org.osgi.service.component.annotations.Component;
#Component(
immediate = true,
property = {
"target.context=exampleContext"
},
service = RouteBuilder.class
)
public class ExampleRouteBuilderService extends RouteBuilder {
#Override
public void configure() throws Exception {
from("timer:exampleTimer?period=3000")
.routeId("exampleTimer")
.log("Hello from Camel using Declarative services");
}
}
#Reference(
target = "(target.context=exampleContext)",
cardinality = ReferenceCardinality.AT_LEAST_ONE,
policyOption = ReferencePolicyOption.GREEDY
)
List<RouteBuilder> routeBuilders;
Just be extra careful when using more advanced options like #Modified or policy = ReferencePolicy.DYNAMIC as these can prevent context from getting recreated when config changes or list gets modified. This can lead to issues like routes getting added twice.
Dependencies for OSGI R6
<dependencies>
<!-- OSGI -->
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.annotation</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.cmpn</artifactId>
<scope>provided</scope>
</dependency>
<!-- Camel -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel.karaf</groupId>
<artifactId>camel-core-osgi</artifactId>
<version>${camel.version}</version>
</dependency>
</dependencies>
Dependencies for OSGI R8
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.core</artifactId>
<version>${osgi.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.component.annotations</artifactId>
<version>1.4.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.metatype.annotations</artifactId>
<version>1.4.0</version>
<scope>provided</scope>
</dependency>
<!-- Camel -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel.karaf</groupId>
<artifactId>camel-core-osgi</artifactId>
<version>${camel.version}</version>
</dependency>
</dependencies>
There is no SCR out of the box, we'll support only OSGi blueprint.
You'll need to build your own scr support or re-use the camel-scr code.
namespace MyQuotesApp
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseDefaultFiles();
app.UseStaticFiles();
//app.Run(async (context) =>
//{
// await context.Response.WriteAsync("Hello World!");
//});
app.UseMvc();
}
}
}
UseDefaultFiles pick these files by default.
default.htm
default.html
index.htm
index.html
If that not worked in your case. You can specify name of your default file with DefaultFilesOptions.
DefaultFilesOptions options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("index.html");
app.UseDefaultFiles(options);
You can also use app.UseFileServer();, it combines the functionality of
app.UseDefaultFiles();
app.UseStaticFiles();
Note: UseDefaultFiles must be called before UseStaticFiles to serve the default file. UseDefaultFiles is a URL re-writer that doesn't actually serve the file. You must enable the static file middleware (UseStaticFiles) to serve the file.
P.S. also update your packages to most latest.
Try this.
app.UseMvc(config =>
{
config.MapRoute(
name: "Default",
template: "{controller=*YourControllerName*}/{action=Index}/{id?}"
);
});
I based my endpoint classes from this example
#Api(name = "tictactoe", version = "v1")
class TicTacToeBase { … }
// TicTacToeA and TicTacToeB both behave as if they have the same #Api annotation as
// TicTacToeBase
class TicTacToeA extends TicTacToeBase { … }
class TicTacToeB extends TicTacToeBase { … }
Endpoints works perfectly with android studio's gradle.
but when I move the source files to my maven-structured project it doesn't recognized TicTacToeA & TicTacToeB as endpoints classes. I've tried to use just simple annotated Endpoint class and it works.
web.xml contents from gradle-structured project is also copied to maven-structured project.
I noticed from target folder of maven-structured project that web.xml was generated but this time only with
<param-value>com.sample.TicTacToeBase</param-value>
shouldn't it be
<param-value>com.sample.TicTacToeA,com.sample.TicTacToeB</param-value>?
You are right! Each API needs to be declared in the web.xml file. Normally you can do this in the file src/main/webapp/WEB-INF/web.xml. (the web.xml file in the target folder is just a copy)
For example, you have a class TicTacToeAPI with annotations which define your API and two subclasses which extend this one.
TicTacToeAPI
#Api(name = "tictactoe",
canonicalName = "TicTacToe API",
version = "v1",
title = "TicTacToe Client API",
description = "TicTacToe Client API.",
namespace = #ApiNamespace(
ownerName = "tictactoe.com",
ownerDomain = "tictactoe.com")
)
public class TicTacToeAPI {
// ...
}
TicTacToeA & TicTacToeB
public class TicTacToeA extends TicTacToeAPI {
// Exposes some methods using annotations.
}
public class TicTacToeB extends TicTacToeAPI {
// Exposes some methods using annotations.
}
In your web.xml file, you cannot declare only the top level class. All subclasses have to be defined.
web.xml
<web-app>
...
<!--Google Cloud Endpoint-->
<servlet>
<servlet-name>SystemServiceServlet</servlet-name>
<servlet-class>
com.google.api.server.spi.SystemServiceServlet
</servlet-class>
<init-param>
<param-name>services</param-name>
<param-value>
com.sample.TicTacToeA,
com.sample.TicTacToeB
</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>SystemServiceServlet</servlet-name>
<url-pattern>/_ah/spi/*</url-pattern>
</servlet-mapping>
...
</web-app>
Alfresco has a MultilingualContentService but unfortunately it is not implemented in the Share UI.
So, how to handle mutilingual content in Share?
(for each document, several files in different languages)
Is there some solution ready?
If I have no choice but to develop, how would you do it?
Wrap it in an object that's accessible from your webscripts. Here's an example which already does it:
package com.someco.web.jscript;
import org.alfresco.repo.jscript.ScriptNode;
import org.alfresco.repo.processor.BaseProcessorExtension;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.ml.MultilingualContentService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.Locale;
public final class MultilingualScript extends BaseProcessorExtension
{
private static final Log logger = LogFactory.getLog(MultilingualScript.class);
private MultilingualContentService multilingualContentService;
private ServiceRegistry serviceRegistry;
public MultilingualScript()
{
if (logger.isDebugEnabled()) {
logger.debug("MultilingualScript Constructor Called");
}
}
//path = path of the original document
//language = required language
//returns the noderef for the translation content for the given language
public ScriptNode multilingualContent(String path, String language, ScriptNode companyHome) {
if (logger.isDebugEnabled()) {
logger.debug("MultilingualScript - parameters - " + path + " , " + language);
}
NodeRef nodeRef = new ScriptNode(companyHome.getNodeRef(), serviceRegistry)
.childByNamePath(path).getNodeRef();
nodeRef = multilingualContentService.getTranslationForLocale(nodeRef, new Locale(language) );
return new ScriptNode(nodeRef, serviceRegistry);
}
public MultilingualContentService getMultilingualContentService() {
return multilingualContentService;
}
public void setMultilingualContentService(
MultilingualContentService multilingualContentService) {
this.multilingualContentService = multilingualContentService;
}
public ServiceRegistry getServiceRegistry() {
return serviceRegistry;
}
public void setServiceRegistry(ServiceRegistry serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
}
The Spring bean:
<bean id="multilingualScript" parent="baseJavaScriptExtension" class="com.someco.web.jscript.MultilingualScript">
<property name="extensionName">
<value>multilingual</value>
</property>
<property name="serviceRegistry">
<ref bean="ServiceRegistry" />
</property>
<property name="multilingualContentService">
<ref bean="MultilingualContentService" />
</property>
</bean>
And finally, use it like this:
var multilingualArticle = multilingual.multilingualContent("/myarticle", "es", companyhome);
I guess showing the actual content shouldn't be to hard, because every content has his own uuid.
The difficulty will be in creating a UI to upload a different language.
The first thing I would do is analyze how the action 'upload new version' works.
So what we need is a custom action to upload a different language file and a popup to select which language that is. So the 'upload new version' does almost exactly the same, you can browse to a file and fill in versioning comment.
I don't know if there are webscripts available in Explorer to store the multilingual content, if not you should develop those.
Secondly is to create a webscript to return you all the multilingual files (same as above, probably won't exist)
Then define a block, like the workflows or version block, so links will appear of the files.