Tuesday, 31 August 2021

Java example to fetch tridion content service PCA graphql response.

- Implementation which is available is built on spring-boot framework, I am gonna put only relative information on graphql and java client.


I have prepared one class which is having few example of tridion pca graphql queries using java,

QueryService.java
@Service
public class QueryService {

final PCAClientProvider pcaClientProvider;

public QueryService(PCAClientProvider pcaClientProvider) {
this.pcaClientProvider = pcaClientProvider;
}

protected ApiClient getPcaClient() {
ApiClient pcaClient = this.pcaClientProvider.getClient();
pcaClient.setDefaultModelType(DataModelType.R2);
pcaClient.setDefaultContentType(ContentType.MODEL);
pcaClient.setModelServiceLinkRenderingType(ModelServiceLinkRendering.RELATIVE);
pcaClient.setTcdlLinkRenderingType(TcdlLinkRendering.RELATIVE);
return pcaClient;
}

protected CustomApiClient getCustomPcaClient() {
CustomApiClient pcaClient = this.pcaClientProvider.getCustomClient();
pcaClient.setDefaultModelType(DataModelType.R2);
pcaClient.setDefaultContentType(ContentType.MODEL);
pcaClient.setModelServiceLinkRenderingType(ModelServiceLinkRendering.RELATIVE);
pcaClient.setTcdlLinkRenderingType(TcdlLinkRendering.RELATIVE);
return pcaClient;
}

public Publication getPublicationTitle(int pubId) {
return getPcaClient().getPublication(ContentNamespace.Sites, pubId, "", null);
}

public Page getPage(int publicationId, int pageId) {
return getPcaClient().getPage(ContentNamespace.Sites, publicationId, pageId, "", ContentIncludeMode.INCLUDE_DATA, null);
}

public PublicationConnection getAllPublications() {
Pagination pagination = new Pagination();
pagination.first = 0;
pagination.after = "";
return getPcaClient().getPublications(ContentNamespace.Sites, pagination, null, "", null);
}

public ComponentPresentation getComponentPresentations(int publicationId, int componentId, int templateId) {
return getPcaClient().getComponentPresentation(ContentNamespace.Sites, publicationId, componentId, templateId, "", ContentIncludeMode.INCLUDE_DATA, null);
}

public BinaryComponent getBinaryComponent(int publicationId, int binaryId) {
return getPcaClient().getBinaryComponent(ContentNamespace.Sites, publicationId, binaryId, "", null);
}

public ComponentPresentationConnection getComponentPresentations(int publicationId) {
Pagination pagination = new Pagination();
pagination.first = 0;
pagination.after = "";
InputComponentPresentationFilter inputComponentPresentationFilter = new InputComponentPresentationFilter();
InputSortParam sortParam = new InputSortParam();
sortParam.setOrder(SortOrderType.Descending);
return getPcaClient().getComponentPresentations(ContentNamespace.Sites, publicationId, inputComponentPresentationFilter,
sortParam, pagination, "", ContentIncludeMode.INCLUDE_DATA, null);
}

public String resolveComponentLink(int publicationId, int targetComponentId, int sourcePageId, int excludeComponentTemplateId, boolean renderRelativeLink) {
return getPcaClient().resolveComponentLink(ContentNamespace.Sites, publicationId, targetComponentId, sourcePageId, excludeComponentTemplateId, renderRelativeLink);
}

public JsonNode getCategories(int publicationId) {
return getCustomPcaClient().getCategories(ContentNamespace.Sites, publicationId);
}
}

for class PCAClientProvider we can take a reference from dxa github repo
https://github.com/RWS/dxa-web-application-java/blob/6e475a85a52d7b8190572662674b43c0d3056fba/dxa-framework/dxa-tridion-common/src/main/java/com/sdl/dxa/tridion/pcaclient/DefaultApiClientProvider.java
we need to have following dependency in the project in order to get PCAClient references.
<dependency>
    <groupId>com.sdl.web.pca</groupId>
    <artifactId>pca-client</artifactId>
    <version>2.2.16</version>
</dependency>


to understand QueryService we can also refer following junit test class
@SpringBootTest
public class QueryServiceTest {

@Autowired
QueryService queryService;

@Test
public void fetchAllPublication() {
PublicationConnection allPublications = queryService.getAllPublications();
assertThat(allPublications, is(notNullValue()));
System.out.printf("total number of web publications are: %d%n", allPublications.getEdges().size());
}

@Test
public void fetchAllCategories() {
int publicationId = 25;
JsonNode categories = queryService.getCategories(publicationId);
assertThat(categories, is(notNullValue()));
System.out.printf("published categories in publication %d%n are: %s%n", publicationId, categories.toPrettyString());
}

@Test
public void fetchBinaryComponent() {
BinaryComponent binaryComponent = queryService.getBinaryComponent(25, 1580);
assertThat(binaryComponent, is(notNullValue()));
System.out.printf("Binary Component id is %s: ", binaryComponent.getId());
}
}

we can also write our own ApiClient in order to get response based on custom query or criteria
for example
public class CustomApiClient extends DefaultApiClient {

private static final Logger LOG = LoggerFactory.getLogger(CustomApiClient.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
private final GraphQLClient graphQLClient;
private final int requestTimeout;

public CustomApiClient(GraphQLClient graphQLClient, int requestTimeout) {
super(graphQLClient, requestTimeout);
this.graphQLClient = graphQLClient;
this.requestTimeout = (int) TimeUnit.MILLISECONDS.toMillis(requestTimeout);
}

public JsonNode getCategories(ContentNamespace ns, int publicationId) {
GraphQLRequest graphQLRequest = (new PCARequestBuilder()).withQuery("AllCategories").withNamespace(ns).withPublicationId(publicationId).withTimeout(this.requestTimeout).build();
Class<JsonNode> clazz = JsonNode.class;
String path = "/data/categories";
return getResultForRequest(graphQLRequest, clazz, path);
}

private <T> T getResultForRequest(GraphQLRequest request, Class<T> clazz, String path) throws ApiClientException {
JsonNode result = getJsonResult(request, path);
try {
return MAPPER.treeToValue(result, clazz);
} catch (JsonProcessingException e) {
throw new ApiClientException("Unable map result to " + clazz.getName() + ": " + result.toString(), e);
}
}

private JsonNode getJsonResult(GraphQLRequest request, String path) throws ApiClientException {
int attempt = 3;
UnauthorizedException[] exception = new UnauthorizedException[1];
while (attempt > 0) {
try {
attempt--;
return getJsonResultInternal(request, path);
} catch (UnauthorizedException ex) {
if (exception[0] == null) exception[0] = ex;
LOG.error("Could not perform query on " + path);
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
throw new ApiClientException("Could not perform query " + request + " after 3 attempts", exception[0]);
}

private JsonNode getJsonResultInternal(GraphQLRequest request, String path) throws ApiClientException, UnauthorizedException {
try {
String resultString = this.graphQLClient.execute(request);
JsonNode resultJson = MAPPER.readTree(resultString);
return resultJson.at(path);
} catch (GraphQLClientException e) {
throw new ApiClientException("Unable to execute query: " + request, e);
} catch (IOException e) {
throw new ApiClientException("Unable to deserialize result for query " + request, e);
}
}
}

Thursday, 22 October 2020

SDL Tridion Sites GraphQL Page query

 Graphql url: http://localhost:8081/cd/api (domain can be changed)


{
  page(namespaceId:1,publicationId:8,pageId:689){
           rawContent{
        data
        content
      }
  }
}

namespaceId (1 is for Tridion Sites and 2 is for Tridion docs)


it will give json data under data keyword and content will be string.

Tuesday, 28 July 2020

SDL Tridion Docs – HTML output customization - create DITA-OT plugin

Objective

Need to customize HTML output when we publish a publication from Tridion Docs.

How to Do it

We need to create DITA-OT plugin which extends html5 plugin

 

Let’s do it

 

What we need is Tridion Docs installed on server😉

And we should be able to publish content on HTML5


Status should be ‘Release Candidate’ or ‘Draft’ after publish a publication


And you will see html output once click on download as below

 

 

 

 

For Map


For Topic


 

 

Let’s do some stuff with this HTML output

-        What we are going to do

-        We’ll apply some CSS on output and also add SDL logo as well.

 

We are going to need custom output format where we’ll publish this publication.

-        Let’s name it like ‘Exercise-html5output

What we are going to learn

  • -        Create output format with style processor on Tridion Docs
  • -        Customize DITA-OT html output

 

Here are steps to create new output format

  • -        Go to Tridion docs installation folder
  • -        %TRIDION_DOCS%\Web\Author\ASP\ClientConfig
  • -        Take backup of MetadataConfig.xml
  • -        Open MetadataConfig.xml and Search for DitaOTTransactionTypeField

o   Add following xml tag on enumlist tag to add transtype on Tridion Docs

         <item>

                <value>exercise-html5</value>

                <label resourceref="FDITAOTTRANSTYPE.ValueList.exercise-html5.Text">exercise-html5</label>

         </item>

  • -        Search for StyleProcessorField on Metadaconfig.xml

o   Add following xml tag to add style processor on Tridion Docs

        <item>

                <value>DITA-OT\Exercise</value>

                <label resourceref="FSTYLEPROCESSOR.ValueList.Exercise.Text">Exercise</label>

         </item>

  • -        To Create Style-processor on Tridion Docs folder

o   Go to %TRIDION_DOCS%\App\Utilities\DITA-OT

o   Copy Infoshare and paste it and rename new folder to Exercise

-        Now Open Tridion Docs webclient

o   Go to Settings -> Output Format

o   Click on new button

o   Add following information on output format window and click ok

o   Now Update publish plug-in setting as this transformtype is not configured on Publish setting
Open Publish Plug-in setting add “exercise-html5” transformation type and save it


Restart service “Trisoft InfoShare BackgroundTask One

  • -        This allows publication to publish on exercise-html5 output format.

 

Let’s Customize HTML format now

-        As we created Style-processor on one previous step, we will create exercise-html5 plugin

-        What is StyleProcessor in Tridion Docs

o   StyleProcessor is DITA-OT for Tridion docs, which process input DITA and render different output based on given transformation type to processor

o   In our case we’ll use transformation type exercise-html5.

-        To Create plugin Go to directory %TRIDION_DOCS%\App\Utilities\DITA-OT\Exercise\plugins
        

-        Right click create New folder give some name to folder which will denote your custom plugin.

o   In my case I’ll give name com.sdl.exercise-html5

 

-        Go to com.sdl.exercise-html5 directory do following steps to develop plugin

o   Create plugin.xml as DITA-OT tool will look for plugin.xml and perform operations based on that

<?xml version="1.0" encoding="UTF-8"?>

<?xml-model href="dita-ot/plugin.rnc" type="application/relax-ng-compact-syntax"?>

 

<plugin id="com.sdl.exercise-html5">

  <feature extension="package.version" value="1.0"/>

  <require plugin="org.dita.html5"/>

 

  <transtype name="exercise-html5" extends="html5" desc="HTML5 Customized" />

</plugin>


o   Require tag on above xml allows plugin to extend “org.dita.html5” plugin which comes with dita-ot.

o   Let’s add some feature to plugin

§  Add <feature extension="dita.conductor.target.relative" file="integrator.xml"/> after transtype tag and we’ll create integrator.xml and write some xml instructions on it.

o   Please visit https://www.dita-ot.org/3.5/topics/html-customization.html to understand xml tags mentioned above.

o   Now we need to provide

§  exercise-html5.xsl

§  map2html5-cover.xsl

§  exercise.css

§  images directory

o   this would look like


o   Let’s look at each file

§  exercise.css

§  exercise-html5.xsl

<?xml version="1.0" encoding="UTF-8"?><!-- This file is part of the DITA Open Toolkit project.

     See the accompanying license.txt file for applicable licenses. --><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:dita-ot="http://dita-ot.sourceforge.net/ns/201007/dita-ot" version="2.0" exclude-result-prefixes="xs dita-ot">


  <xsl:import href="plugin:org.dita.html5:xsl/dita2html5.xsl"/>

  

  <xsl:template match="*" mode="chapterBody">

    <body>

      <xsl:apply-templates select="." mode="addAttributesToHtmlBodyElement"/>

      <xsl:call-template name="setaname"/>  <!-- For HTML4 compatibility, if needed -->

      <xsl:value-of select="$newline"/>

      <xsl:apply-templates select="." mode="addHeaderToHtmlBodyElement"/>


      <!-- Include a user's XSL call here to generate a toc based on what's a child of topic -->

      <xsl:call-template name="gen-user-sidetoc"/>


  <nav><div class="milogo-wrapper"><a href="index.html"><img src="images/SDL_Logo.png"/></a></div></nav>

      <xsl:apply-templates select="." mode="addContentToHtmlBodyElement"/>

      <xsl:apply-templates select="." mode="addFooterToHtmlBodyElement"/>

    </body>

    <xsl:value-of select="$newline"/>

  </xsl:template>

  

<xsl:template match="*[contains(@class, ' topic/image ')]/@height">

  <xsl:variable name="height-in-pixel">

    <xsl:call-template name="length-to-pixels">

      <xsl:with-param name="dimen" select="."/>

    </xsl:call-template>

  </xsl:variable>

  <xsl:if test="not($height-in-pixel = '100%')">

    <xsl:attribute name="height">

      <!--xsl:choose>

        <xsl:when test="../@scale and string(number(../@scale))!='NaN'">          

          <xsl:value-of select="number($height-in-pixel) * number(../@scale)"/>

        </xsl:when>

        <xsl:otherwise-->

          <xsl:value-of select="number($height-in-pixel)*2"/>

        <!--/xsl:otherwise>

      </xsl:choose-->

    </xsl:attribute>

  </xsl:if>  

</xsl:template>


<xsl:template match="*[contains(@class, ' topic/image ')]/@width">

  <xsl:variable name="width-in-pixel">

    <xsl:call-template name="length-to-pixels">

      <xsl:with-param name="dimen" select="."/>

    </xsl:call-template>

  </xsl:variable>

  <xsl:if test="not($width-in-pixel = '100%')">

    <xsl:attribute name="width">

      <!--xsl:choose>

        <xsl:when test="../@scale and string(number(../@scale))!='NaN'">          

          <xsl:value-of select="number($width-in-pixel) * number(../@scale)"/>

        </xsl:when>

        <xsl:otherwise-->

          <xsl:value-of select="number($width-in-pixel)*2"/>

        <!--/xsl:otherwise>

      </xsl:choose-->

    </xsl:attribute>

  </xsl:if>  

</xsl:template>

    

  <xsl:output xmlns:dita="http://dita-ot.sourceforge.net" method="html" encoding="UTF-8" indent="no" doctype-system="about:legacy-compat" omit-xml-declaration="yes"/>


</xsl:stylesheet>


§  map2html5-cover.xsl

<?xml version="1.0" encoding="UTF-8"?>

<!-- This file is part of the DITA Open Toolkit project.

     See the accompanying license.txt file for applicable licenses. -->

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">


  <xsl:import href="plugin:org.dita.html5:xsl/dita2html5.xsl"/>

  <xsl:import href="plugin:org.dita.xhtml:xsl/map2html-coverImpl.xsl"/>

  <xsl:import href="plugin:com.sdl.exercise-html5:xsl/exercise-nav.xsl"/>


  


  <xsl:output xmlns:dita="http://dita-ot.sourceforge.net" method="html" encoding="UTF-8" doctype-system="about:legacy-compat" omit-xml-declaration="yes"/>


</xsl:stylesheet>


§  exercise-nav.xsl

<?xml version="1.0" encoding="UTF-8"?><!-- This file is part of the DITA Open Toolkit project.

     See the accompanying license.txt file for applicable licenses. --><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:dita-ot="http://dita-ot.sourceforge.net/ns/201007/dita-ot" version="2.0" exclude-result-prefixes="xs dita-ot">


  

  <xsl:import href="plugin:org.dita.xhtml:xsl/map2html-coverImpl.xsl"/>

  

  <xsl:template match="*[contains(@class, ' map/map ')]" mode="toc" priority="10">

    <xsl:param name="pathFromMaplist"/>

    <xsl:if test="descendant::*[contains(@class, ' map/topicref ')]                                [not(@toc = 'no')]                                [not(@processing-role = 'resource-only')]">

      <nav>

  <div class="milogo-wrapper"><a href="index.html"><img src="images/SDL_Logo.png"/></a>test</div>

        <ul>

          <xsl:call-template name="commonattributes"/>

          <xsl:apply-templates select="*[contains(@class, ' map/topicref ')]" mode="toc">

            <xsl:with-param name="pathFromMaplist" select="$pathFromMaplist"/>

          </xsl:apply-templates>

        </ul>

      </nav>

    </xsl:if>

  </xsl:template>

  

  

  <xsl:template match="*[contains(@class, ' map/topicref ')]                         [not(@toc = 'no')]                         [not(@processing-role = 'resource-only')]" mode="toc">

    <xsl:param name="pathFromMaplist"/>

    <xsl:variable name="title">

      <xsl:apply-templates select="." mode="get-navtitle"/>

    </xsl:variable>

    <xsl:choose>

      <xsl:when test="normalize-space($title)">

        <li>

          <xsl:call-template name="commonattributes"/>

          <xsl:choose>

            <!-- If there is a reference to a DITA or HTML file, and it is not external: -->

            <xsl:when test="normalize-space(@href)">

              <a>

                <xsl:attribute name="href">

                  <xsl:choose>

                    <xsl:when test="@copy-to and not(contains(@chunk, 'to-content')) and                                      (not(@format) or @format = 'dita' or @format = 'ditamap') ">

                      <xsl:if test="not(@scope = 'external')">

                        <xsl:value-of select="$pathFromMaplist"/>

                      </xsl:if>

                      <xsl:call-template name="replace-extension">

                        <xsl:with-param name="filename" select="@copy-to"/>

                        <xsl:with-param name="extension" select="$OUTEXT"/>

                      </xsl:call-template>

                      <xsl:if test="not(contains(@copy-to, '#')) and contains(@href, '#')">

                        <xsl:value-of select="concat('#', substring-after(@href, '#'))"/>

                      </xsl:if>

                    </xsl:when>

                    <xsl:when test="not(@scope = 'external') and (not(@format) or @format = 'dita' or @format = 'ditamap')">

                      <xsl:if test="not(@scope = 'external')">

                        <xsl:value-of select="$pathFromMaplist"/>

                      </xsl:if>

                      <xsl:call-template name="replace-extension">

                        <xsl:with-param name="filename" select="@href"/>

                        <xsl:with-param name="extension" select="$OUTEXT"/>

                      </xsl:call-template>

                    </xsl:when>

                    <xsl:otherwise><!-- If non-DITA, keep the href as-is -->

                      <xsl:if test="not(@scope = 'external')">

                        <xsl:value-of select="$pathFromMaplist"/>

                      </xsl:if>

                      <xsl:value-of select="@href"/>

                    </xsl:otherwise>

                  </xsl:choose>

                </xsl:attribute>

                <xsl:if test="@scope = 'external' or not(not(@format) or @format = 'dita' or @format = 'ditamap')">

                  <xsl:attribute name="target">_blank</xsl:attribute>

                </xsl:if>

                <xsl:value-of select="$title"/>

              </a>

            </xsl:when>

            <xsl:otherwise>

<xsl:choose>

<xsl:when test="$title and $title != ''">

   <h2><xsl:value-of select="$title"/></h2>

</xsl:when>

<xsl:otherwise>

  <xsl:value-of select="$title"/>

</xsl:otherwise>

</xsl:choose>

            </xsl:otherwise>

          </xsl:choose>

          <!-- If there are any children that should be in the TOC, process them -->

          <xsl:if test="descendant::*[contains(@class, ' map/topicref ')]                                      [not(@toc = 'no')]                                      [not(@processing-role = 'resource-only')]">

            <ul>

              <xsl:apply-templates select="*[contains(@class, ' map/topicref ')]" mode="toc">

                <xsl:with-param name="pathFromMaplist" select="$pathFromMaplist"/>

              </xsl:apply-templates>

            </ul>

          </xsl:if>

        </li>

      </xsl:when>

      <xsl:otherwise><!-- if it is an empty topicref -->

        <xsl:apply-templates select="*[contains(@class, ' map/topicref ')]" mode="toc">

          <xsl:with-param name="pathFromMaplist" select="$pathFromMaplist"/>

        </xsl:apply-templates>

      </xsl:otherwise>

    </xsl:choose>

  </xsl:template>

  

  <xsl:output xmlns:dita="http://dita-ot.sourceforge.net" method="html" encoding="UTF-8" indent="no" doctype-system="about:legacy-compat" omit-xml-declaration="yes"/>


</xsl:stylesheet>

§  this will customize your html output.

o   Let’s create new output format for publication and select exercise-html5 from dropdown

o   Click next select language and click create.

o   Now select output format and click publish

o   Index.html would look like

o   And topic page would look like