Liferay create a custom PanelCategory for multiple portlet - eclipse

I use Liferay 7.2 and Liferay IDE (eclipse). I created two separate Liferay admin portlet to creating a view for the database entries. I added in the first portlet "Teachers" a new panel called school with this generated code in application.list Package.
here is the code of - PanelApp.java
#Component(
immediate = true,
property = {
"panel.app.order:Integer=300",
"panel.category.key=" + TeachersPanelCategoryKeys.CONTROL_PANEL_CATEGORY
},
service = PanelApp.class
)
public class TeachersPanelApp extends BasePanelApp {
#Override
public String getPortletId() {
return TeachersPortletKeys.TEACHERS;
}
#Override
#Reference(
target = "(javax.portlet.name=" + TeachersPortletKeys.TEACHERS+ ")",
unbind = "-"
)
public void setPortlet(Portlet portlet) {
super.setPortlet(portlet);
}
}
.
public class TeachersPanelCategoryKeys {
public static final String CONTROL_PANEL_CATEGORY = "Teachers";
}
And here is the code of - PanelCategory.java
#Component(
immediate = true,
property = {
"panel.category.key=" + PanelCategoryKeys.SITE_ADMINISTRATION,
"panel.category.order:Integer=300"
},
service = PanelCategory.class)
public class TeachersPanelCategory extends BasePanelCategory {
#Override
public String getKey() {
return TeachersPanelCategoryKeys.CONTROL_PANEL_CATEGORY;
}
#Override
public String getLabel(Locale locale) {
return LanguageUtil.get(locale, "School");
}}
And here is the code of portlet.java
#Component(
immediate = true,
property = {
"com.liferay.portlet.add-default-resource=true",
"com.liferay.portlet.display-category=category.hidden",
"com.liferay.portlet.header-portlet-css=/css/main.css",
"com.liferay.portlet.layout-cacheable=true",
"com.liferay.portlet.private-request-attributes=false",
"com.liferay.portlet.private-session-attributes=false",
"com.liferay.portlet.render-weight=50",
"com.liferay.portlet.use-default-template=true",
"javax.portlet.display-name=Teachers",
"javax.portlet.expiration-cache=0",
"javax.portlet.init-param.template-path=/",
"javax.portlet.init-param.view-template=/view.jsp",
"javax.portlet.name=" + TeachersPortletKeys.TEACHERS,
"javax.portlet.resource-bundle=content.Language",
"javax.portlet.security-role-ref=power-user,user",
},
service = Portlet.class
)
public class TeachersPortlet extends MVCPortlet {
// some code to get entries from db
#Override
public void doView(final RenderRequest renderRequest, final RenderResponse renderResponse)
throws IOException, PortletException {
// some code
Now I want to add the second created portlet "Students" under the same Panel "School". I created it in the same way as "Teachers" but now I have two school panel. As it is shown in the image below.
I just want to display one panel category called school that contain both Teachers and Students in the list.
I do not know how I can think to do that.

As you're implementing a TeachersPanelCategory, I'm assuming you're also implementing a StudentsPanelCategory. From a naming perspective, I'd have expected a SchoolPanelCategory.
I'm currently at a loss of how the ControlPanel portlets actually declare their associated panel, but that place would be where you pick the common "school" panel and use the same spelling for both.
In other words: If you deploy two panels with the same name, I'd expect exactly what you document here. Make sure you're only deploying one of them
Edit: I'd like to know what TeachersPanelCategoryKeys.CONTROL_PANEL_CATEGORY is defined as, and the corresponding (assumed, not shown) StudentsPanelCategoryKeys.CONTROL_PANEL_CATEGORY. Both categories have the same label, but if they have different keys, they'll be different. I'm not sure what happens when you deploy two components with the same key: You should deploy only one.
Edit2: I've missed the code before: You're producing the key to your first category as "Teachers", and the label as "School". I'm assuming that the key for your other category is "Students". Liferay organizes the categories by key - and if the keys are different, then you'll end up with two different categories. Make their key more similar to their name (e.g. create a single SchoolCategory and associate your portlets/panelApps with that:
#Component(
immediate = true,
property = {
"panel.category.key=" + PanelCategoryKeys.SITE_ADMINISTRATION,
"panel.category.order:Integer=300"
},
service = PanelCategory.class)
public class SchoolPanelCategory extends BasePanelCategory {
#Override
public String getKey() {
return "school"; // this is the category that you want to associate with
}
#Override
public String getLabel(Locale locale) {
return LanguageUtil.get(locale, "School");
}
}
and
#Component(
immediate = true,
property = {
"panel.app.order:Integer=300",
"panel.category.key=school" // referencing the category created above
// (use the same for your StudentsPanelApp)
},
service = PanelApp.class
)
public class TeachersPanelApp extends BasePanelApp {
#Override
public String getPortletId() {
return TeachersPortletKeys.TEACHERS;
}
#Override
#Reference(
target = "(javax.portlet.name=" + TeachersPortletKeys.TEACHERS+ ")",
unbind = "-"
)
public void setPortlet(Portlet portlet) {
super.setPortlet(portlet);
}
}
(See the one-line comments within the code for the critical lines. Replace with proper constants if you like)

Related

Unable to fetch OSGi Configuration Values

I have created an event handler and used OSGi configuration as below.
#Component(immediate = true,
service=EventHandler.class,
property= {
EventConstants.EVENT_TOPIC + "=" + ReplicationAction.EVENT_TOPIC
}
)
#Designate(ocd = PagePublishEventHandler.Configuration.class)
public class PagePublishEventHandler implements EventHandler {
private static String rootPage = "";
#Override
public void handleEvent(final Event event) {
}
#Activate
#Modified
public void activate(Configuration config) {
String rootPage = config.getPath();
logger.info("********ConfigurationPropertyInterface**********activate**********************");
logger.info("********rootPage********",rootPage);
}
#ObjectClassDefinition(name="AEM Plugin OSGi Configuration")
public #interface Configuration {
#AttributeDefinition(
name = "Root Page For Web Site",
description = "Configurable paths for root page",
type = AttributeType.STRING
)
String getPath() default "/content";
}
}
Inside the activate method, Value of rootPage is always blank. Do anyone has the solution on this.
I
Thanks
I ran your code in my machine and I don't see any issues with it except for the following things which I noticed.
The rootPage is defined as a static variable. Though this is not the cause of the issue in question, it might cause issues during runtime.
You are not printing the value of the rootPage in your log (probably that is why you think that the value is null?). In order to print it, use format specifiers as shown below.
logger.info("********rootPage******** {}",rootPage);

how to search any keyword from string using jfce AutoCompleteField

I have swt text where in I have written like "new AutoCompleteField (textSearch,new TextContentProvider(), searchList); it works but it finds the strings start with expression. I want to create my own proposal provider where i can write something if my string contains any keyword, i should get autoComplete popup.
You can't use the existing AutoCompleteField for this since you need to change the content proposal provider.
A suitable IContentProposalProvider would be something like:
public class AnyPositionContentProposalProvider implements IContentProposalProvider
{
private final String [] proposals;
public AnyPositionContentProposalProvider(String [] theProposals)
{
proposals = theProposals;
}
#Override
public IContentProposal [] getProposals(String contents, int position)
{
List<IContentProposal> result = new ArrayList<>();
for (String proposal : proposals) {
if (proposal.contains(contents)) {
result.add(new ContentProposal(proposal));
}
}
return result.toArray(new IContentProposal [result.size()]);
}
}
The following methods set this up to work like AutoCompleteField:
// Installs on a Text control
public static void installAnyPositionMatch(Text control, String [] proposals)
{
installAnyPositionMatch(control, new TextContentAdapter(), proposals);
}
// Install on any control with a content adapter
public static void installAnyPositionMatch(Control control, IControlContentAdapter controlContentAdapter, String [] proposals)
{
IContentProposalProvider proposalProvider = new AnyPositionContentProposalProvider(proposals);
ContentProposalAdapter adapter = new ContentProposalAdapter(control, controlContentAdapter, proposalProvider, null, null);
adapter.setPropagateKeys(true);
adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
}

Add my own rules in SonarQube with RPG

I want to create my own SonarQube Plugin for the RPG language. I have the following problem.
I start by created the RpgLanguage class that extends to AbstractLanguage. In this class, I defined my new language "Rpg". You can see my class in the following code :
public class RpgLanguage extends AbstractLanguage{
public static final String KEY = "rpg";
private Settings settings;
public RpgLanguage(Settings settings) {
super(KEY, "Rpg");
this.settings = settings;
}
public String[] getFileSuffixes() {
String[] suffixes = settings.getStringArray("");
if (suffixes == null || suffixes.length == 0) {
suffixes = StringUtils.split(".RPG", ",");
}
return suffixes;
}
}
After, I have created my RpgRulesDefinition class that implements RulesDefinition. In this class, I create a new repository for the language RPG and I want to add a rule in this repository (empty rules). The code is like below :
public static final String REPOSITORY_KEY = "rpg_repository_mkoza";
public void define(Context context) {
NewRepository repo = context.createRepository(REPOSITORY_KEY, "rpg");
repo.setName("Mkoza Analyser rules RPG");
// We could use a XML or JSON file to load all rule metadata, but
// we prefer use annotations in order to have all information in a single place
RulesDefinitionAnnotationLoader annotationLoader = new RulesDefinitionAnnotationLoader();
annotationLoader.load(repo, RpgFileCheckRegistrar.checkClasses());
repo.done();
}
My class RpgFileCheckRegistrar that call my Rules :
/**
* Register the classes that will be used to instantiate checks during analysis.
*/
public void register(RegistrarContext registrarContext) {
// Call to registerClassesForRepository to associate the classes with the correct repository key
registrarContext.registerClassesForRepository(RpgRulesDefinition.REPOSITORY_KEY, Arrays.asList(checkClasses()), Arrays.asList(testCheckClasses()));
}
/**
* Lists all the checks provided by the plugin
*/
public static Class<? extends JavaCheck>[] checkClasses() {
return new Class[] {
RulesExampleCheck.class
};
}
/**
* Lists all the test checks provided by the plugin
*/
public static Class<? extends JavaCheck>[] testCheckClasses() {
return new Class[] {};
}
My Rule class (still empty):
#Rule(
key = "Rule1",
name = "Rule that make nothing",
priority = Priority.MAJOR,
tags = {"example"}
)
public class RulesExampleCheck extends BaseTreeVisitor{
/**
* Right in java code your rule
*/
}
And the class SonarPlugin that call all these extensions :
public final class RpgSonarPlugin extends SonarPlugin
{
// This is where you're going to declare all your Sonar extensions
public List getExtensions() {
return Arrays.asList(
RpgLanguage.class,
RpgRulesDefinition.class,
RpgFileCheckRegistrar.class
);
}
}
The problem when I want to start the server sonar, I obtain this error stack :
Exception sending context initialized event to listener instance of class org.sonar.server.platform.PlatformServletContextListener
java.lang.IllegalStateException: One of HTML description or Markdown description must be defined for rule [repository=rpg_repository_mkoza, key=Rule1]
I try different things but I don't understand why there are these error.
Of course I want that my repository "rpg_repository_mkoza" is display in the RPG's repository in SonarQube with the Rules : RulesExampleCheck.
My sonar-plugin-version is the 3.7.1
I find my problem. There are need to add the field 'description' in #Rule.
For example :
#Rule(
key = "Rule1",
name = "RuleExampleCheck",
description = "This rule do nothing",
priority = Priority.INFO,
tags = {"try"}
)

How do I tell a GWT cell widget data has changed via the Event Bus?

I have a GWT Cell Tree that I use to display a file structure from a CMS. I am using a AsyncDataProvider that loads data from a custom RPC class I created. I also have a Web Socket system that will broadcast events (File create, renamed, moved, deleted etc) from other clients also working in the system.
What I am trying to wrap my head around is when I recieve one of these events, how I correctly update my Cell Tree?
I suppose this problem would be analogus to having two instances of my Cell Tree on the page, which are presenting the same server-side data and wanting to ensure that when the user updated one, that the other updated as well, via using the EventBus.
I feel this should be pretty simple but I have spent about 6 hours on it now with no headway. My code is included below:
NOTE: I am not using RequestFactory even though it may look like I am it is my custom RPC framework. Also, FileEntity is just a simple representation of a file which has a name accessible by getName().
private void drawTree() {
// fileService is injected earlier on and is my own custom rpc service
TreeViewModel model = new CustomTreeModel(new FileDataProvider(fileService));
CellTree tree = new CellTree(model, "Root");
tree.setAnimationEnabled(true);
getView().getWorkspace().add(tree);
}
private static class CustomTreeModel implements TreeViewModel {
// I am trying to use a single AsyncDataProvider so I have a single point of loading data which I can manipulate (Not sure if this is the correct way to go)
public CustomTreeModel(FileDataProvider dataProvider) {
this.provider = provider;
}
public <T> NodeInfo<?> getNodeInfo(final T value) {
if (!(value instanceof FileEntity)) {
// I already have the root File loaded in my presenter, if we are at the root of the tree, I just add it via a list here
ListDataProvider<FileEntity> dataProvider = new ListDataProvider<FileEntity>();
dataProvider.getList().add(TreeWorkspacePresenter.rootFolder);
return new DefaultNodeInfo<FileEntity>(dataProvider,
new FileCell());
} else {
// Otherwise I know that we are loading some tree child data, and I invoke the AsyncProvider to load it from the server
provider.setFocusFile(value);
return new DefaultNodeInfo<FileEntity>(provider,
new FileCell());
}
}
public boolean isLeaf(Object value) {
if(value == null || value instanceof Folder)
return false;
return true;
}
}
public class FileDataProvider extends AsyncDataProvider<FileEntity> {
private FileEntity focusFile;
private FileService service;
#Inject
public FileDataProvider(FileService service){
this.service = service;
}
public void setFocusFile(FileEntity focusFile){
this.focusFile = focusFile;
}
#Override
protected void onRangeChanged(HasData<FileEntity> display) {
service.getChildren(((Folder) focusFile),
new Reciever<List<FileEntity>>() {
#Override
public void onSuccess(List<FileEntity> files) {
updateRowData(0, files);
}
#Override
public void onFailure(Throwable error) {
Window.alert(error.toString());
}
});
}
}
/**
* The cell used to render Files.
*/
public static class FileCell extends AbstractCell<FileEntity> {
private FileEntity file;
public FileEntity getFile() {
return file;
}
#Override
public void render(Context context, FileEntity file, SafeHtmlBuilder sb) {
if (file != null) {
this.file = file;
sb.appendEscaped(file.getName());
}
}
}
Currently there is no direct support for individual tree item refresh even in the latest gwt version.
But there is a workaround for this. Each tree item is associated with an value. Using this value you can get the corresponding tree item.
In your case, i assume, you know which item to update/refresh ie you know which File Entity has changed. Use this file entity to search for the corresponding tree item. Once you get the tree item you just need to expand and collapse or collapse and expand its parent item. This makes parent item to re-render its children. Your changed file entity is one among the children. So it get refreshed.
public void refreshFileEntity(FileEntity fileEntity)
{
TreeNode fileEntityNode = getFileEntityNode(fileEntity, cellTree.getRootTreeNode()
// For expnad and collapse run this for loop
for ( int i = 0; i < fileEntityNode.getParent().getChildCount(); i++ )
{
if ( !fileEntityNode.getParent().isChildLeaf( i ) )
{
fileEntityNode.getParent().setChildOpen( i, true );
}
}
}
public TreeNode getFileEntityNode(FileEntity fileEntity, TreeNode treeNode)
{
if(treeNode.getChildren == null)
{
return null;
}
for(TreeNode node : treeNode.getChildren())
{
if(fileEntity.getId().equals( node.getValue.getId() ))
{
return node;
}
getEntityNode(fileEntity, node);
}
}
You can use the dataprovider to update the celltree.
You can update the complete cell tree with:
provider.setList(pList);
provider.refresh();
If you want to update only a special cell you can get the listwrapper from the dataprovider and only set one element.
provider.getList().set(12, element);

Place name without a colon (:)?

Still a bit of a GWT noob here but making progress using Activities and Places as described by Google here.
I understand that a Place's "URL consists of the Place's simple class name (like "HelloPlace") followed by a colon (:) and the token returned by the PlaceTokenizer.
Can I somehow remove the colon when I don't have a token to send?
For example, I'm fine with a URL like this "#editPerson:2" when I need to work with PersonId=2. But what about when I just want to present a blank Person form? In that case I would prefer to use "#addPersonForm" rather than "#addPersonForm:"
Any suggestions (even better code suggestions) would be most appreciated!
You can provide your own PlaceHistoryMapper (without using the generator) as already suggested by Boris_siroB, or you can do it within a PlaceTokenizer with an empty prefix: with an empty prefix, there won't be a colon, and the tokenizer can do whatever you want. If you totally distinct places, make it a tokenizer of Place, so it's also the "catchall" for getToken. That way you can keep all the advantages of the generation with prefixes, PlaceTokenizers and WithTokenizers (if you want to take advantage of them)
To take full control of the URL hash (that is to generate your own tokens from Places and map these tokens back to Places) you can implement your own history mapper (a class implementing the PlaceHistoryMapper interface).
public class MyPlaceHistoryMapper implements PlaceHistoryMapper {
#Override
public Place getPlace(String token) {
// parse tokens and create Places here
}
#Override
public String getToken(Place place) {
// examine Places and compose tokens here
}
}
In your entry point class you'd then replace the line:
AppPlaceHistoryMapper historyMapper = GWT.create(AppPlaceHistoryMapper.class);
with:
PlaceHistoryMapper appHistoryMapper = new MyPlaceHistoryMapper();
That's it. Your URL hashes no longer need to be class name-based or to use the : delimiter.
I'm using a PlaceHistoryMapper decorator named PlaceHistoryMapperWithoutColon.
Usage :
final PlaceHistoryMapper historyMapper0 = GWT
.create(PlaceHistoryMapperImpl.class);
final PlaceHistoryMapper historyMapper = new PlaceHistoryMapperWithoutColon(historyMapper0);
Decorator source :
public class PlaceHistoryMapperWithoutColon implements PlaceHistoryMapper {
private static final String COLON = ":";
private PlaceHistoryMapper placeHistoryMapper;
public PlaceHistoryMapperWithoutColon(PlaceHistoryMapper placeHistoryMapper) {
this.placeHistoryMapper = placeHistoryMapper;
}
#Override
public Place getPlace(String token) {
if (token != null && !token.endsWith(COLON)) {
token = token.concat(COLON);
}
return placeHistoryMapper.getPlace(token);
}
#Override
public String getToken(Place place) {
String token = placeHistoryMapper.getToken(place);
if (token != null && token.endsWith(COLON)) {
token = token.substring(0, token.length() - 1);
}
return token;
}
}
Decorated source example :
#WithTokenizers({ FirstPlace.Tokenizer.class, SecondPlace.Tokenizer.class })
public interface PlaceHistoryMapperImpl extends PlaceHistoryMapper {
}
Place source example :
public final class FirstPlace extends Place {
#Prefix("first")
public static class Tokenizer implements PlaceTokenizer<FirstPlace> {
#Override
public NetworkInfosPlace getPlace(String token) {
return new FirstPlace ();
}
#Override
public String getToken(FirstPlace place) {
return "";
}
}
}